// Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved.
// isulad-tools is licensed under the Mulan PSL v1.
// You can use this software according to the terms and conditions of the Mulan PSL v1.
// You may obtain a copy of Mulan PSL v1 at:
//    http://license.coscl.org.cn/MulanPSL
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
// PURPOSE.
// See the Mulan PSL v1 for more details.
// Description: mount operation
// Author: zhangwei
// Create: 2018-01-18

package mount

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

	docker_mount "github.com/docker/docker/pkg/mount"
)

// Mount is mount operation
func Mount(device, target, mType, options string) error {
	flagint, data := docker_mount.ParseOptions(options)
	flag := uintptr(flagint)
	// propagation option
	propagationFlags := (uintptr)(syscall.MS_SLAVE | syscall.MS_SHARED | syscall.MS_UNBINDABLE | syscall.MS_PRIVATE)

	if err := syscall.Mount(device, target, mType, flag&^propagationFlags, data); err != nil {
		return err
	}

	// If we have a bind mount or remount, remount...
	if flag&syscall.MS_BIND == syscall.MS_BIND && flag&syscall.MS_RDONLY == syscall.MS_RDONLY {
		return syscall.Mount(device, target, mType, flag|syscall.MS_REMOUNT, data)
	}
	if flag&propagationFlags != 0 {
		return syscall.Mount("none", target, "none", flag&propagationFlags, data)
	}
	return nil
}

// Unmount is unmount operation
func Unmount(target string) error {
	return syscall.Unmount(target, syscall.MNT_DETACH)
}

// ValidMountPropagation checks propagation of path
func ValidMountPropagation(path, mOpt string) error {
	var bind, slave, shared bool
	var slavemnt, sharedmnt bool
	for _, opt := range strings.Split(mOpt, ",") {
		if opt == "bind" {
			bind = true
		}
		if opt == "shared" {
			shared = true
		}
		if opt == "slave" {
			slave = true
		}
	}
	if !bind {
		return nil
	}

	source, options, err := getSource(path)
	if err != nil {
		return err
	}

	for _, opt := range strings.Split(options, " ") {
		if strings.HasPrefix(opt, "shared:") {
			sharedmnt = true
			break
		}
		if strings.HasPrefix(opt, "master:") {
			slavemnt = true
			break
		}
	}
	if shared && !sharedmnt {
		return fmt.Errorf("Path %s is mounted on %s but it is not a shared mount", path, source)
	}
	if slave && !sharedmnt && !slavemnt {
		return fmt.Errorf("Path %s is mounted on %s but it is not a shared or slave mount", path, source)
	}
	return nil
}

func getSource(sourcepath string) (string, string, error) {
	path, err := filepath.EvalSymlinks(sourcepath)
	if err != nil {
		return "", "", err
	}

	mountinfos, err := docker_mount.GetMounts()
	if err != nil {
		return "", "", err
	}

	for {
		for _, m := range mountinfos {
			if m.Mountpoint == path {
				return path, m.Optional, nil
			}
		}
		if path == "/" {
			return "", "", fmt.Errorf("Could not find mount %s", sourcepath)
		}
		path = filepath.Dir(path)
	}
	return "", "", fmt.Errorf("Unexpected error in getMouont")
}
