package createconfig

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/containers/libpod/libpod"
	"github.com/containers/libpod/pkg/util"
	"github.com/containers/storage/pkg/stringid"
	spec "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

const (
	// TypeBind is the type for mounting host dir
	TypeBind = "bind"
	// TypeVolume is the type for named volumes
	TypeVolume = "volume"
	// TypeTmpfs is the type for mounting tmpfs
	TypeTmpfs = "tmpfs"
)

var (
	errDuplicateDest = errors.Errorf("duplicate mount destination")
	optionArgError   = errors.Errorf("must provide an argument for option")
	noDestError      = errors.Errorf("must set volume destination")
)

// Parse all volume-related options in the create config into a set of mounts
// and named volumes to add to the container.
// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags.
// TODO: Named volume options -  should we default to rprivate? It bakes into a
// bind mount under the hood...
// TODO: handle options parsing/processing via containers/storage/pkg/mount
func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, []*libpod.ContainerNamedVolume, error) {
	// Add image volumes.
	baseMounts, baseVolumes, err := config.getImageVolumes()
	if err != nil {
		return nil, nil, err
	}

	// Add --volumes-from.
	// Overrides image volumes unconditionally.
	vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime)
	if err != nil {
		return nil, nil, err
	}
	for dest, mount := range vFromMounts {
		baseMounts[dest] = mount
	}
	for dest, volume := range vFromVolumes {
		baseVolumes[dest] = volume
	}

	// Next mounts from the --mounts flag.
	// Do not override yet.
	unifiedMounts, unifiedVolumes, err := config.getMounts()
	if err != nil {
		return nil, nil, err
	}

	// Next --volumes flag.
	// Do not override yet.
	volumeMounts, volumeVolumes, err := config.getVolumeMounts()
	if err != nil {
		return nil, nil, err
	}

	// Next --tmpfs flag.
	// Do not override yet.
	tmpfsMounts, err := config.getTmpfsMounts()
	if err != nil {
		return nil, nil, err
	}

	// Unify mounts from --mount, --volume, --tmpfs.
	// Also add mounts + volumes directly from createconfig.
	// Start with --volume.
	for dest, mount := range volumeMounts {
		if _, ok := unifiedMounts[dest]; ok {
			return nil, nil, errors.Wrapf(errDuplicateDest, dest)
		}
		unifiedMounts[dest] = mount
	}
	for dest, volume := range volumeVolumes {
		if _, ok := unifiedVolumes[dest]; ok {
			return nil, nil, errors.Wrapf(errDuplicateDest, dest)
		}
		unifiedVolumes[dest] = volume
	}
	// Now --tmpfs
	for dest, tmpfs := range tmpfsMounts {
		if _, ok := unifiedMounts[dest]; ok {
			return nil, nil, errors.Wrapf(errDuplicateDest, dest)
		}
		unifiedMounts[dest] = tmpfs
	}
	// Now spec mounts and volumes
	for _, mount := range config.Mounts {
		dest := mount.Destination
		if _, ok := unifiedMounts[dest]; ok {
			return nil, nil, errors.Wrapf(errDuplicateDest, dest)
		}
		unifiedMounts[dest] = mount
	}
	for _, volume := range config.NamedVolumes {
		dest := volume.Dest
		if _, ok := unifiedVolumes[dest]; ok {
			return nil, nil, errors.Wrapf(errDuplicateDest, dest)
		}
		unifiedVolumes[dest] = volume
	}

	// If requested, add container init binary
	if config.Init {
		initPath := config.InitPath
		if initPath == "" {
			rtc, err := runtime.GetConfig()
			if err != nil {
				return nil, nil, err
			}
			initPath = rtc.InitPath
		}
		initMount, err := config.addContainerInitBinary(initPath)
		if err != nil {
			return nil, nil, err
		}
		if _, ok := unifiedMounts[initMount.Destination]; ok {
			return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination)
		}
		unifiedMounts[initMount.Destination] = initMount
	}

	// Before superceding, we need to find volume mounts which conflict with
	// named volumes, and vice versa.
	// We'll delete the conflicts here as we supercede.
	for dest := range unifiedMounts {
		if _, ok := baseVolumes[dest]; ok {
			delete(baseVolumes, dest)
		}
	}
	for dest := range unifiedVolumes {
		if _, ok := baseMounts[dest]; ok {
			delete(baseMounts, dest)
		}
	}

	// Supercede volumes-from/image volumes with unified volumes from above.
	// This is an unconditional replacement.
	for dest, mount := range unifiedMounts {
		baseMounts[dest] = mount
	}
	for dest, volume := range unifiedVolumes {
		baseVolumes[dest] = volume
	}

	// If requested, add tmpfs filesystems for read-only containers.
	// Need to keep track of which we created, so we don't modify options
	// for them later...
	readonlyTmpfs := map[string]bool{
		"/tmp":     false,
		"/var/tmp": false,
		"/run":     false,
	}
	if config.ReadOnlyRootfs && config.ReadOnlyTmpfs {
		options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup", "size=65536k"}
		for dest := range readonlyTmpfs {
			if _, ok := baseMounts[dest]; ok {
				continue
			}
			localOpts := options
			if dest == "/run" {
				localOpts = append(localOpts, "noexec")
			}
			baseMounts[dest] = spec.Mount{
				Destination: dest,
				Type:        "tmpfs",
				Source:      "tmpfs",
				Options:     localOpts,
			}
			readonlyTmpfs[dest] = true
		}
	}

	// Check for conflicts between named volumes and mounts
	for dest := range baseMounts {
		if _, ok := baseVolumes[dest]; ok {
			return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
		}
	}
	for dest := range baseVolumes {
		if _, ok := baseMounts[dest]; ok {
			return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
		}
	}

	// Final step: maps to arrays
	finalMounts := make([]spec.Mount, 0, len(baseMounts))
	for _, mount := range baseMounts {
		// All user-added tmpfs mounts need their options processed.
		// Exception: mounts added by the ReadOnlyTmpfs option, which
		// contain several exceptions to normal options rules.
		if mount.Type == TypeTmpfs && !readonlyTmpfs[mount.Destination] {
			opts, err := util.ProcessTmpfsOptions(mount.Options)
			if err != nil {
				return nil, nil, err
			}
			mount.Options = opts
		}
		finalMounts = append(finalMounts, mount)
	}
	finalVolumes := make([]*libpod.ContainerNamedVolume, 0, len(baseVolumes))
	for _, volume := range baseVolumes {
		finalVolumes = append(finalVolumes, volume)
	}

	logrus.Debugf("Got mounts: %v", finalMounts)
	logrus.Debugf("Got volumes: %v", finalVolumes)

	return finalMounts, finalVolumes, nil
}

// Parse volumes from - a set of containers whose volumes we will mount in.
// Grab the containers, retrieve any user-created spec mounts and all named
// volumes, and return a list of them.
// Conflicts are resolved simply - the last container specified wins.
// Container names may be suffixed by mount options after a colon.
func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
	// TODO: This can probably be disabled now
	if os.Geteuid() != 0 {
		return nil, nil, nil
	}

	// Both of these are maps of mount destination to mount type.
	// We ensure that each destination is only mounted to once in this way.
	finalMounts := make(map[string]spec.Mount)
	finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)

	for _, vol := range config.VolumesFrom {
		options := []string{}
		splitVol := strings.SplitN(vol, ":", 2)
		if len(splitVol) == 2 {
			if strings.Contains(splitVol[1], "Z") ||
				strings.Contains(splitVol[1], "private") ||
				strings.Contains(splitVol[1], "slave") ||
				strings.Contains(splitVol[1], "shared") {
				return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z", splitVol[1])
			}
			options = strings.Split(splitVol[1], ",")
			opts, err := ValidateVolumeOpts(options)
			if err != nil {
				return nil, nil, err
			}
			options = opts
		}
		ctr, err := runtime.LookupContainer(splitVol[0])
		if err != nil {
			return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0])
		}

		logrus.Debugf("Adding volumes from container %s", ctr.ID())

		// Look up the container's user volumes. This gets us the
		// destinations of all mounts the user added to the container.
		userVolumesArr := ctr.UserVolumes()

		// We're going to need to access them a lot, so convert to a map
		// to reduce looping.
		// We'll also use the map to indicate if we missed any volumes along the way.
		userVolumes := make(map[string]bool)
		for _, dest := range userVolumesArr {
			userVolumes[dest] = false
		}

		// Now we get the container's spec and loop through its volumes
		// and append them in if we can find them.
		spec := ctr.Spec()
		if spec == nil {
			return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID())
		}
		for _, mnt := range spec.Mounts {
			if mnt.Type != TypeBind {
				continue
			}
			if _, exists := userVolumes[mnt.Destination]; exists {
				userVolumes[mnt.Destination] = true

				if len(options) != 0 {
					mnt.Options = options
				}

				if _, ok := finalMounts[mnt.Destination]; ok {
					logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
				}
				finalMounts[mnt.Destination] = mnt
			}
		}

		// We're done with the spec mounts. Add named volumes.
		// Add these unconditionally - none of them are automatically
		// part of the container, as some spec mounts are.
		namedVolumes := ctr.NamedVolumes()
		for _, namedVol := range namedVolumes {
			if _, exists := userVolumes[namedVol.Dest]; exists {
				userVolumes[namedVol.Dest] = true
			}

			if len(options) != 0 {
				namedVol.Options = options
			}

			if _, ok := finalMounts[namedVol.Dest]; ok {
				logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID())
			}
			finalNamedVolumes[namedVol.Dest] = namedVol
		}

		// Check if we missed any volumes
		for volDest, found := range userVolumes {
			if !found {
				logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
			}
		}
	}

	return finalMounts, finalNamedVolumes, nil
}

// getMounts takes user-provided input from the --mount flag and creates OCI
// spec mounts and Libpod named volumes.
// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
// podman run --mount type=tmpfs,target=/dev/shm ...
// podman run --mount type=volume,source=test-volume, ...
func (config *CreateConfig) getMounts() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
	finalMounts := make(map[string]spec.Mount)
	finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)

	errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")

	// TODO(vrothberg): the manual parsing can be replaced with a regular expression
	//                  to allow a more robust parsing of the mount format and to give
	//                  precise errors regarding supported format versus suppored options.
	for _, mount := range config.MountsFlag {
		arr := strings.SplitN(mount, ",", 2)
		if len(arr) < 2 {
			return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
		}
		kv := strings.Split(arr[0], "=")
		// TODO: type is not explicitly required in Docker.
		// If not specified, it defaults to "volume".
		if len(kv) != 2 || kv[0] != "type" {
			return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
		}

		tokens := strings.Split(arr[1], ",")
		switch kv[1] {
		case TypeBind:
			mount, err := getBindMount(tokens)
			if err != nil {
				return nil, nil, err
			}
			if _, ok := finalMounts[mount.Destination]; ok {
				return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
			}
			finalMounts[mount.Destination] = mount
		case TypeTmpfs:
			mount, err := getTmpfsMount(tokens)
			if err != nil {
				return nil, nil, err
			}
			if _, ok := finalMounts[mount.Destination]; ok {
				return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
			}
			finalMounts[mount.Destination] = mount
		case "volume":
			volume, err := getNamedVolume(tokens)
			if err != nil {
				return nil, nil, err
			}
			if _, ok := finalNamedVolumes[volume.Dest]; ok {
				return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest)
			}
			finalNamedVolumes[volume.Dest] = volume
		default:
			return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1])
		}
	}

	return finalMounts, finalNamedVolumes, nil
}

// Parse a single bind mount entry from the --mount flag.
func getBindMount(args []string) (spec.Mount, error) {
	newMount := spec.Mount{
		Type: TypeBind,
	}

	setSource := false
	setDest := false

	for _, val := range args {
		kv := strings.Split(val, "=")
		switch kv[0] {
		case "bind-nonrecursive":
			newMount.Options = append(newMount.Options, "bind")
		case "ro", "nosuid", "nodev", "noexec":
			// TODO: detect duplication of these options.
			// (Is this necessary?)
			newMount.Options = append(newMount.Options, kv[0])
		case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
			newMount.Options = append(newMount.Options, kv[0])
		case "bind-propagation":
			if len(kv) == 1 {
				return newMount, errors.Wrapf(optionArgError, kv[0])
			}
			newMount.Options = append(newMount.Options, kv[1])
		case "src", "source":
			if len(kv) == 1 {
				return newMount, errors.Wrapf(optionArgError, kv[0])
			}
			if err := ValidateVolumeHostDir(kv[1]); err != nil {
				return newMount, err
			}
			newMount.Source = kv[1]
			setSource = true
		case "target", "dst", "destination":
			if len(kv) == 1 {
				return newMount, errors.Wrapf(optionArgError, kv[0])
			}
			if err := ValidateVolumeCtrDir(kv[1]); err != nil {
				return newMount, err
			}
			newMount.Destination = kv[1]
			setDest = true
		default:
			return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
		}
	}

	if !setDest {
		return newMount, noDestError
	}

	if !setSource {
		newMount.Source = newMount.Destination
	}

	opts, err := ValidateVolumeOpts(newMount.Options)
	if err != nil {
		return newMount, err
	}
	newMount.Options = opts

	return newMount, nil
}

// Parse a single tmpfs mount entry from the --mount flag
func getTmpfsMount(args []string) (spec.Mount, error) {
	newMount := spec.Mount{
		Type:   TypeTmpfs,
		Source: TypeTmpfs,
	}

	setDest := false

	for _, val := range args {
		kv := strings.Split(val, "=")
		switch kv[0] {
		case "ro", "nosuid", "nodev", "noexec":
			newMount.Options = append(newMount.Options, kv[0])
		case "tmpfs-mode":
			if len(kv) == 1 {
				return newMount, errors.Wrapf(optionArgError, kv[0])
			}
			newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1]))
		case "tmpfs-size":
			if len(kv) == 1 {
				return newMount, errors.Wrapf(optionArgError, kv[0])
			}
			newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1]))
		case "src", "source":
			return newMount, errors.Errorf("source is not supported with tmpfs mounts")
		case "target", "dst", "destination":
			if len(kv) == 1 {
				return newMount, errors.Wrapf(optionArgError, kv[0])
			}
			if err := ValidateVolumeCtrDir(kv[1]); err != nil {
				return newMount, err
			}
			newMount.Destination = kv[1]
			setDest = true
		default:
			return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
		}
	}

	if !setDest {
		return newMount, noDestError
	}

	return newMount, nil
}

// Parse a single volume mount entry from the --mount flag.
// Note that the volume-label option for named volumes is currently NOT supported.
// TODO: add support for --volume-label
func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) {
	newVolume := new(libpod.ContainerNamedVolume)

	setSource := false
	setDest := false

	for _, val := range args {
		kv := strings.Split(val, "=")
		switch kv[0] {
		case "ro", "nosuid", "nodev", "noexec":
			// TODO: detect duplication of these options
			newVolume.Options = append(newVolume.Options, kv[0])
		case "volume-label":
			return nil, errors.Errorf("the --volume-label option is not presently implemented")
		case "src", "source":
			if len(kv) == 1 {
				return nil, errors.Wrapf(optionArgError, kv[0])
			}
			newVolume.Name = kv[1]
			setSource = true
		case "target", "dst", "destination":
			if len(kv) == 1 {
				return nil, errors.Wrapf(optionArgError, kv[0])
			}
			if err := ValidateVolumeCtrDir(kv[1]); err != nil {
				return nil, err
			}
			newVolume.Dest = kv[1]
			setDest = true
		default:
			return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
		}
	}

	if !setSource {
		return nil, errors.Errorf("must set source volume")
	}
	if !setDest {
		return nil, noDestError
	}

	return newVolume, nil
}

// ValidateVolumeHostDir validates a volume mount's source directory
func ValidateVolumeHostDir(hostDir string) error {
	if len(hostDir) == 0 {
		return errors.Errorf("host directory cannot be empty")
	}
	if filepath.IsAbs(hostDir) {
		if _, err := os.Stat(hostDir); err != nil {
			return errors.Wrapf(err, "error checking path %q", hostDir)
		}
	}
	// If hostDir is not an absolute path, that means the user wants to create a
	// named volume. This will be done later on in the code.
	return nil
}

// ValidateVolumeCtrDir validates a volume mount's destination directory.
func ValidateVolumeCtrDir(ctrDir string) error {
	if len(ctrDir) == 0 {
		return errors.Errorf("container directory cannot be empty")
	}
	if !filepath.IsAbs(ctrDir) {
		return errors.Errorf("invalid container path %q, must be an absolute path", ctrDir)
	}
	return nil
}

// ValidateVolumeOpts validates a volume's options
func ValidateVolumeOpts(options []string) ([]string, error) {
	var foundRootPropagation, foundRWRO, foundLabelChange, bindType int
	finalOpts := make([]string, 0, len(options))
	for _, opt := range options {
		switch opt {
		case "rw", "ro":
			foundRWRO++
			if foundRWRO > 1 {
				return nil, errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", strings.Join(options, ", "))
			}
		case "z", "Z":
			foundLabelChange++
			if foundLabelChange > 1 {
				return nil, errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", strings.Join(options, ", "))
			}
		case "private", "rprivate", "shared", "rshared", "slave", "rslave":
			foundRootPropagation++
			if foundRootPropagation > 1 {
				return nil, errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", strings.Join(options, ", "))
			}
		case "bind", "rbind":
			bindType++
			if bindType > 1 {
				return nil, errors.Errorf("invalid options %q, can only specify 1 '[r]bind' option", strings.Join(options, ", "))
			}
		case "cached", "delegated":
			// The discarded ops are OS X specific volume options
			// introduced in a recent Docker version.
			// They have no meaning on Linux, so here we silently
			// drop them. This matches Docker's behavior (the options
			// are intended to be always safe to use, even not on OS
			// X).
			continue
		default:
			return nil, errors.Errorf("invalid mount option %q", opt)
		}
		finalOpts = append(finalOpts, opt)
	}
	return finalOpts, nil
}

// GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
	mounts := make(map[string]spec.Mount)
	volumes := make(map[string]*libpod.ContainerNamedVolume)

	volumeFormatErr := errors.Errorf("incorrect volume format, should be host-dir:ctr-dir[:option]")

	for _, vol := range config.Volumes {
		var (
			options []string
			src     string
			dest    string
		)

		splitVol := strings.Split(vol, ":")
		if len(splitVol) > 3 {
			return nil, nil, errors.Wrapf(volumeFormatErr, vol)
		}

		src = splitVol[0]
		if len(splitVol) == 1 {
			dest = src
		} else if len(splitVol) > 1 {
			dest = splitVol[1]
		}
		if len(splitVol) > 2 {
			options = strings.Split(splitVol[2], ",")
			opts, err := ValidateVolumeOpts(options)
			if err != nil {
				return nil, nil, err
			}
			options = opts
		}

		if err := ValidateVolumeHostDir(src); err != nil {
			return nil, nil, err
		}
		if err := ValidateVolumeCtrDir(dest); err != nil {
			return nil, nil, err
		}

		if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
			// This is not a named volume
			newMount := spec.Mount{
				Destination: dest,
				Type:        string(TypeBind),
				Source:      src,
				Options:     options,
			}
			if _, ok := mounts[newMount.Destination]; ok {
				return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
			}
			mounts[newMount.Destination] = newMount
		} else {
			// This is a named volume
			newNamedVol := new(libpod.ContainerNamedVolume)
			newNamedVol.Name = src
			newNamedVol.Dest = dest
			newNamedVol.Options = options

			if _, ok := volumes[newNamedVol.Dest]; ok {
				return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
			}
			volumes[newNamedVol.Dest] = newNamedVol
		}

		logrus.Debugf("User mount %s:%s options %v", src, dest, options)
	}

	return mounts, volumes, nil
}

// Get mounts for container's image volumes
func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) {
	mounts := make(map[string]spec.Mount)
	volumes := make(map[string]*libpod.ContainerNamedVolume)

	if config.ImageVolumeType == "ignore" {
		return mounts, volumes, nil
	}

	for vol := range config.BuiltinImgVolumes {
		if config.ImageVolumeType == "tmpfs" {
			// Tmpfs image volumes are handled as mounts
			mount := spec.Mount{
				Destination: vol,
				Source:      TypeTmpfs,
				Type:        TypeTmpfs,
				Options:     []string{"rprivate", "rw", "nodev"},
			}
			mounts[vol] = mount
		} else {
			namedVolume := new(libpod.ContainerNamedVolume)
			namedVolume.Name = stringid.GenerateNonCryptoID()
			namedVolume.Options = []string{"rprivate", "rw", "nodev"}
			namedVolume.Dest = vol
			volumes[vol] = namedVolume
		}
	}

	return mounts, volumes, nil
}

// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
func (config *CreateConfig) getTmpfsMounts() (map[string]spec.Mount, error) {
	m := make(map[string]spec.Mount)
	for _, i := range config.Tmpfs {
		// Default options if nothing passed
		var options []string
		spliti := strings.Split(i, ":")
		destPath := spliti[0]
		if len(spliti) > 1 {
			options = strings.Split(spliti[1], ",")
		}

		if _, ok := m[destPath]; ok {
			return nil, errors.Wrapf(errDuplicateDest, destPath)
		}

		mount := spec.Mount{
			Destination: destPath,
			Type:        string(TypeTmpfs),
			Options:     options,
			Source:      string(TypeTmpfs),
		}
		m[destPath] = mount
	}
	return m, nil
}

// AddContainerInitBinary adds the init binary specified by path iff the
// container will run in a private PID namespace that is not shared with the
// host or another pre-existing container, where an init-like process is
// already running.
//
// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command
// to execute the bind-mounted binary as PID 1.
func (config *CreateConfig) addContainerInitBinary(path string) (spec.Mount, error) {
	mount := spec.Mount{
		Destination: "/dev/init",
		Type:        TypeBind,
		Source:      path,
		Options:     []string{TypeBind, "ro"},
	}

	if path == "" {
		return mount, fmt.Errorf("please specify a path to the container-init binary")
	}
	if !config.PidMode.IsPrivate() {
		return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
	}
	if config.Systemd {
		return mount, fmt.Errorf("cannot use container-init binary with systemd")
	}
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return mount, errors.Wrap(err, "container-init binary not found on the host")
	}
	config.Command = append([]string{"/dev/init", "--"}, config.Command...)
	return mount, nil
}

// Supercede existing mounts in the spec with new, user-specified mounts.
// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
// one mount, and we already have /tmp/a and /tmp/b, should we remove
// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
	if len(mounts) > 0 {
		// If we have overlappings mounts, remove them from the spec in favor of
		// the user-added volume mounts
		destinations := make(map[string]bool)
		for _, mount := range mounts {
			destinations[path.Clean(mount.Destination)] = true
		}
		// Copy all mounts from spec to defaultMounts, except for
		//  - mounts overridden by a user supplied mount;
		//  - all mounts under /dev if a user supplied /dev is present;
		mountDev := destinations["/dev"]
		for _, mount := range configMount {
			if _, ok := destinations[path.Clean(mount.Destination)]; !ok {
				if mountDev && strings.HasPrefix(mount.Destination, "/dev/") {
					// filter out everything under /dev if /dev is user-mounted
					continue
				}

				logrus.Debugf("Adding mount %s", mount.Destination)
				mounts = append(mounts, mount)
			}
		}
		return mounts
	}
	return configMount
}

// Ensure mount options on all mounts are correct
func initFSMounts(inputMounts []spec.Mount) []spec.Mount {
	var mounts []spec.Mount
	for _, m := range inputMounts {
		if m.Type == TypeBind {
			m.Options = util.ProcessOptions(m.Options)
		}
		if m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev" {
			m.Options = append(m.Options, "tmpcopyup")
		}
		mounts = append(mounts, m)
	}
	return mounts
}