<commit_before>package validate

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/Sirupsen/logrus"
	"github.com/blang/semver"
	rspec "github.com/opencontainers/runtime-spec/specs-go"
)

const specConfig = "config.json"

var (
	defaultRlimits = []string{
		"RLIMIT_CPU",
		"RLIMIT_FSIZE",
		"RLIMIT_DATA",
		"RLIMIT_STACK",
		"RLIMIT_CORE",
		"RLIMIT_RSS",
		"RLIMIT_NPROC",
		"RLIMIT_NOFILE",
		"RLIMIT_MEMLOCK",
		"RLIMIT_AS",
		"RLIMIT_LOCKS",
		"RLIMIT_SIGPENDING",
		"RLIMIT_MSGQUEUE",
		"RLIMIT_NICE",
		"RLIMIT_RTPRIO",
		"RLIMIT_RTTIME",
	}
	defaultCaps = []string{
		"CAP_CHOWN",
		"CAP_DAC_OVERRIDE",
		"CAP_FSETID",
		"CAP_FOWNER",
		"CAP_MKNOD",
		"CAP_NET_RAW",
		"CAP_SETGID",
		"CAP_SETUID",
		"CAP_SETFCAP",
		"CAP_SETPCAP",
		"CAP_NET_BIND_SERVICE",
		"CAP_SYS_CHROOT",
		"CAP_KILL",
		"CAP_AUDIT_WRITE",
	}
)

type Validator struct {
	spec         *rspec.Spec
	bundlePath   string
	HostSpecific bool
}

func NewValidator(spec *rspec.Spec, bundlePath string, hostSpecific bool) Validator {
	return Validator{spec: spec, bundlePath: bundlePath, HostSpecific: hostSpecific}
}

func NewValidatorFromPath(bundlePath string, hostSpecific bool) (Validator, error) {
	if bundlePath == "" {
		return Validator{}, fmt.Errorf("Bundle path shouldn't be empty")
	}

	if _, err := os.Stat(bundlePath); err != nil {
		return Validator{}, err
	}

	configPath := filepath.Join(bundlePath, specConfig)
	content, err := ioutil.ReadFile(configPath)
	if err != nil {
		return Validator{}, err
	}
	if !utf8.Valid(content) {
		return Validator{}, fmt.Errorf("%q is not encoded in UTF-8", configPath)
	}
	var spec rspec.Spec
	if err = json.Unmarshal(content, &spec); err != nil {
		return Validator{}, err
	}

	return NewValidator(&spec, bundlePath, hostSpecific), nil
}

func (v *Validator) CheckAll() (msgs []string) {
	msgs = append(msgs, v.CheckRootfsPath()...)
	msgs = append(msgs, v.CheckMandatoryFields()...)
	msgs = append(msgs, v.CheckSemVer()...)
	msgs = append(msgs, v.CheckMounts()...)
	msgs = append(msgs, v.CheckPlatform()...)
	msgs = append(msgs, v.CheckProcess()...)
	msgs = append(msgs, v.CheckLinux()...)
	msgs = append(msgs, v.CheckHooks()...)

	return
}

func (v *Validator) CheckRootfsPath() (msgs []string) {
	logrus.Debugf("check rootfs path")

	var rootfsPath string
	if filepath.IsAbs(v.spec.Root.Path) {
		rootfsPath = v.spec.Root.Path
	} else {
		rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
	}

	if fi, err := os.Stat(rootfsPath); err != nil {
		msgs = append(msgs, fmt.Sprintf("Cannot find the root path %q", rootfsPath))
	} else if !fi.IsDir() {
		msgs = append(msgs, fmt.Sprintf("The root path %q is not a directory.", rootfsPath))
	}

	return

}
func (v *Validator) CheckSemVer() (msgs []string) {
	logrus.Debugf("check semver")

	version := v.spec.Version
	_, err := semver.Parse(version)
	if err != nil {
		msgs = append(msgs, fmt.Sprintf("%q is not valid SemVer: %s", version, err.Error()))
	}
	if version != rspec.Version {
		msgs = append(msgs, fmt.Sprintf("internal error: validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version))
	}

	return
}

func (v *Validator) CheckPlatform() (msgs []string) {
	logrus.Debugf("check platform")

	validCombins := map[string][]string{
		"android":   {"arm"},
		"darwin":    {"386", "amd64", "arm", "arm64"},
		"dragonfly": {"amd64"},
		"freebsd":   {"386", "amd64", "arm"},
		"linux":     {"386", "amd64", "arm", "arm64", "ppc64", "ppc64le", "mips64", "mips64le", "s390x"},
		"netbsd":    {"386", "amd64", "arm"},
		"openbsd":   {"386", "amd64", "arm"},
		"plan9":     {"386", "amd64"},
		"solaris":   {"amd64"},
		"windows":   {"386", "amd64"}}
	platform := v.spec.Platform
	for os, archs := range validCombins {
		if os == platform.OS {
			for _, arch := range archs {
				if arch == platform.Arch {
					return nil
				}
			}
			msgs = append(msgs, fmt.Sprintf("Combination of %q and %q is invalid.", platform.OS, platform.Arch))
		}
	}
	msgs = append(msgs, fmt.Sprintf("Operation system %q of the bundle is not supported yet.", platform.OS))

	return
}

func (v *Validator) CheckHooks() (msgs []string) {
	logrus.Debugf("check hooks")

	msgs = append(msgs, checkEventHooks("pre-start", v.spec.Hooks.Prestart, v.HostSpecific)...)
	msgs = append(msgs, checkEventHooks("post-start", v.spec.Hooks.Poststart, v.HostSpecific)...)
	msgs = append(msgs, checkEventHooks("post-stop", v.spec.Hooks.Poststop, v.HostSpecific)...)

	return
}

func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (msgs []string) {
	for _, hook := range hooks {
		if !filepath.IsAbs(hook.Path) {
			msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not absolute path", hookType, hook.Path))
		}

		if hostSpecific {
			fi, err := os.Stat(hook.Path)
			if err != nil {
				msgs = append(msgs, fmt.Sprintf("Cannot find %s hook: %v", hookType, hook.Path))
			}
			if fi.Mode()&0111 == 0 {
				msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not executable", hookType, hook.Path))
			}
		}

		for _, env := range hook.Env {
			if !envValid(env) {
				msgs = append(msgs, fmt.Sprintf("Env %q for hook %v is in the invalid form.", env, hook.Path))
			}
		}
	}

	return
}

func (v *Validator) CheckProcess() (msgs []string) {
	logrus.Debugf("check process")

	process := v.spec.Process
	if !filepath.IsAbs(process.Cwd) {
		msgs = append(msgs, fmt.Sprintf("cwd %q is not an absolute path", process.Cwd))
	}

	for _, env := range process.Env {
		if !envValid(env) {
			msgs = append(msgs, fmt.Sprintf("env %q should be in the form of 'key=value'. The left hand side must consist solely of letters, digits, and underscores '_'.", env))
		}
	}

	if len(process.Args) == 0 {
		msgs = append(msgs, fmt.Sprintf("args must not be empty"))
	} else {
		if filepath.IsAbs(process.Args[0]) {
			var rootfsPath string
			if filepath.IsAbs(v.spec.Root.Path) {
				rootfsPath = v.spec.Root.Path
			} else {
				rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
			}
			absPath := filepath.Join(rootfsPath, process.Args[0])
			fileinfo, err := os.Stat(absPath)
			if os.IsNotExist(err) {
				logrus.Warnf("executable %q is not available in rootfs currently", process.Args[0])
			} else if err != nil {
				msgs = append(msgs, err.Error())
			} else {
				m := fileinfo.Mode()
				if m.IsDir() || m&0111 == 0 {
					msgs = append(msgs, fmt.Sprintf("arg %q is not executable", process.Args[0]))
				}
			}
		}
	}

	for index := 0; index < len(process.Capabilities); index++ {
		capability := process.Capabilities[index]
		if !capValid(capability) {
			msgs = append(msgs, fmt.Sprintf("capability %q is not valid, man capabilities(7)", process.Capabilities[index]))
		}
	}

	for index := 0; index < len(process.Rlimits); index++ {
		if !rlimitValid(process.Rlimits[index].Type) {
			msgs = append(msgs, fmt.Sprintf("rlimit type %q is invalid.", process.Rlimits[index].Type))
		}
		if process.Rlimits[index].Hard < process.Rlimits[index].Soft {
			msgs = append(msgs, fmt.Sprintf("hard limit of rlimit %s should not be less than soft limit.", process.Rlimits[index].Type))
		}
	}

	if len(process.ApparmorProfile) > 0 {
		profilePath := filepath.Join(v.bundlePath, v.spec.Root.Path, "/etc/apparmor.d", process.ApparmorProfile)
		_, err := os.Stat(profilePath)
		if err != nil {
			msgs = append(msgs, err.Error())
		}
	}

	return
}

func supportedMountTypes(OS string, hostSpecific bool) (map[string]bool, error) {
	supportedTypes := make(map[string]bool)

	if OS != "linux" && OS != "windows" {
		logrus.Warnf("%v is not supported to check mount type", OS)
		return nil, nil
	} else if OS == "windows" {
		supportedTypes["ntfs"] = true
		return supportedTypes, nil
	}

	if hostSpecific {
		f, err := os.Open("/proc/filesystems")
		if err != nil {
			return nil, err
		}
		defer f.Close()

		s := bufio.NewScanner(f)
		for s.Scan() {
			if err := s.Err(); err != nil {
				return supportedTypes, err
			}

			text := s.Text()
			parts := strings.Split(text, "\t")
			if len(parts) > 1 {
				supportedTypes[parts[1]] = true
			} else {
				supportedTypes[parts[0]] = true
			}
		}

		supportedTypes["bind"] = true

		return supportedTypes, nil
	}
	logrus.Warn("Checking linux mount types without --host-specific is not supported yet")
	return nil, nil
}

func (v *Validator) CheckMounts() (msgs []string) {
	logrus.Debugf("check mounts")

	supportedTypes, err := supportedMountTypes(v.spec.Platform.OS, v.HostSpecific)
	if err != nil {
		msgs = append(msgs, err.Error())
		return
	}

	if supportedTypes != nil {
		for _, mount := range v.spec.Mounts {
			if !supportedTypes[mount.Type] {
				msgs = append(msgs, fmt.Sprintf("Unsupported mount type %q", mount.Type))
			}

			if !filepath.IsAbs(mount.Destination) {
				msgs = append(msgs, fmt.Sprintf("destination %v is not an absolute path", mount.Destination))
			}
		}
	}

	return
}

//Linux only
func (v *Validator) CheckLinux() (msgs []string) {
	logrus.Debugf("check linux")

	utsExists := false
	ipcExists := false
	mountExists := false
	netExists := false
	userExists := false

	for index := 0; index < len(v.spec.Linux.Namespaces); index++ {
		if !namespaceValid(v.spec.Linux.Namespaces[index]) {
			msgs = append(msgs, fmt.Sprintf("namespace %v is invalid.", v.spec.Linux.Namespaces[index]))
		} else if len(v.spec.Linux.Namespaces[index].Path) == 0 {
			if v.spec.Linux.Namespaces[index].Type == rspec.UTSNamespace {
				utsExists = true
			} else if v.spec.Linux.Namespaces[index].Type == rspec.IPCNamespace {
				ipcExists = true
			} else if v.spec.Linux.Namespaces[index].Type == rspec.NetworkNamespace {
				netExists = true
			} else if v.spec.Linux.Namespaces[index].Type == rspec.MountNamespace {
				mountExists = true
			} else if v.spec.Linux.Namespaces[index].Type == rspec.UserNamespace {
				userExists = true
			}
		}
	}

	if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !userExists {
		msgs = append(msgs, "UID/GID mappings requires a new User namespace to be specified as well")
	} else if len(v.spec.Linux.UIDMappings) > 5 {
		msgs = append(msgs, "Only 5 UID mappings are allowed (linux kernel restriction).")
	} else if len(v.spec.Linux.GIDMappings) > 5 {
		msgs = append(msgs, "Only 5 GID mappings are allowed (linux kernel restriction).")
	}

	for k := range v.spec.Linux.Sysctl {
		if strings.HasPrefix(k, "net.") && !netExists {
			msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new Network namespace to be specified as well", k))
		}
		if strings.HasPrefix(k, "fs.mqueue.") {
			if !mountExists || !ipcExists {
				msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k))
			}
		}
	}

	if v.spec.Platform.OS == "linux" && !utsExists && v.spec.Hostname != "" {
		msgs = append(msgs, fmt.Sprintf("On Linux, hostname requires a new UTS namespace to be specified as well"))
	}

	// Linux devices validation
	devList := make(map[string]bool)
	typeList := make(map[string]bool)
	for index := 0; index < len(v.spec.Linux.Devices); index++ {
		device := v.spec.Linux.Devices[index]
		if !deviceValid(device) {
			msgs = append(msgs, fmt.Sprintf("device %v is invalid.", device))
		}

		if _, exists := devList[device.Path]; exists {
			msgs = append(msgs, fmt.Sprintf("device %s is duplicated", device.Path))
		} else {
			var rootfsPath string
			if filepath.IsAbs(v.spec.Root.Path) {
				rootfsPath = v.spec.Root.Path
			} else {
				rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
			}
			absPath := filepath.Join(rootfsPath, device.Path)
			_, err := os.Stat(absPath)
			if os.IsNotExist(err) {
				devList[device.Path] = true
			} else if err != nil {
				msgs = append(msgs, err.Error())
			} else {
				msgs = append(msgs, fmt.Sprintf("%s already exists in filesystem", device.Path))
			}
		}

		// unify u->c when comparing, they are synonyms
		var devID string
		if device.Type == "u" {
			devID = fmt.Sprintf("%s:%d:%d", "c", device.Major, device.Minor)
		} else {
			devID = fmt.Sprintf("%s:%d:%d", device.Type, device.Major, device.Minor)
		}

		if _, exists := typeList[devID]; exists {
			logrus.Warnf("type:%s, major:%d and minor:%d for linux devices is duplicated", device.Type, device.Major, device.Minor)
		} else {
			typeList[devID] = true
		}
	}

	if v.spec.Linux.Resources != nil {
		ms := v.CheckLinuxResources()
		msgs = append(msgs, ms...)
	}

	if v.spec.Linux.Seccomp != nil {
		ms := v.CheckSeccomp()
		msgs = append(msgs, ms...)<commit_msg>validate: add unmatched device validation<commit_after>package validate

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"syscall"
	"unicode"
	"unicode/utf8"

	"github.com/Sirupsen/logrus"
	"github.com/blang/semver"
	rspec "github.com/opencontainers/runtime-spec/specs-go"
)

const specConfig = "config.json"

var (
	defaultRlimits = []string{
		"RLIMIT_CPU",
		"RLIMIT_FSIZE",
		"RLIMIT_DATA",
		"RLIMIT_STACK",
		"RLIMIT_CORE",
		"RLIMIT_RSS",
		"RLIMIT_NPROC",
		"RLIMIT_NOFILE",
		"RLIMIT_MEMLOCK",
		"RLIMIT_AS",
		"RLIMIT_LOCKS",
		"RLIMIT_SIGPENDING",
		"RLIMIT_MSGQUEUE",
		"RLIMIT_NICE",
		"RLIMIT_RTPRIO",
		"RLIMIT_RTTIME",
	}
	defaultCaps = []string{
		"CAP_CHOWN",
		"CAP_DAC_OVERRIDE",
		"CAP_FSETID",
		"CAP_FOWNER",
		"CAP_MKNOD",
		"CAP_NET_RAW",
		"CAP_SETGID",
		"CAP_SETUID",
		"CAP_SETFCAP",
		"CAP_SETPCAP",
		"CAP_NET_BIND_SERVICE",
		"CAP_SYS_CHROOT",
		"CAP_KILL",
		"CAP_AUDIT_WRITE",
	}
)

type Validator struct {
	spec         *rspec.Spec
	bundlePath   string
	HostSpecific bool
}

func NewValidator(spec *rspec.Spec, bundlePath string, hostSpecific bool) Validator {
	return Validator{spec: spec, bundlePath: bundlePath, HostSpecific: hostSpecific}
}

func NewValidatorFromPath(bundlePath string, hostSpecific bool) (Validator, error) {
	if bundlePath == "" {
		return Validator{}, fmt.Errorf("Bundle path shouldn't be empty")
	}

	if _, err := os.Stat(bundlePath); err != nil {
		return Validator{}, err
	}

	configPath := filepath.Join(bundlePath, specConfig)
	content, err := ioutil.ReadFile(configPath)
	if err != nil {
		return Validator{}, err
	}
	if !utf8.Valid(content) {
		return Validator{}, fmt.Errorf("%q is not encoded in UTF-8", configPath)
	}
	var spec rspec.Spec
	if err = json.Unmarshal(content, &spec); err != nil {
		return Validator{}, err
	}

	return NewValidator(&spec, bundlePath, hostSpecific), nil
}

func (v *Validator) CheckAll() (msgs []string) {
	msgs = append(msgs, v.CheckRootfsPath()...)
	msgs = append(msgs, v.CheckMandatoryFields()...)
	msgs = append(msgs, v.CheckSemVer()...)
	msgs = append(msgs, v.CheckMounts()...)
	msgs = append(msgs, v.CheckPlatform()...)
	msgs = append(msgs, v.CheckProcess()...)
	msgs = append(msgs, v.CheckLinux()...)
	msgs = append(msgs, v.CheckHooks()...)

	return
}

func (v *Validator) CheckRootfsPath() (msgs []string) {
	logrus.Debugf("check rootfs path")

	var rootfsPath string
	if filepath.IsAbs(v.spec.Root.Path) {
		rootfsPath = v.spec.Root.Path
	} else {
		rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
	}

	if fi, err := os.Stat(rootfsPath); err != nil {
		msgs = append(msgs, fmt.Sprintf("Cannot find the root path %q", rootfsPath))
	} else if !fi.IsDir() {
		msgs = append(msgs, fmt.Sprintf("The root path %q is not a directory.", rootfsPath))
	}

	return

}
func (v *Validator) CheckSemVer() (msgs []string) {
	logrus.Debugf("check semver")

	version := v.spec.Version
	_, err := semver.Parse(version)
	if err != nil {
		msgs = append(msgs, fmt.Sprintf("%q is not valid SemVer: %s", version, err.Error()))
	}
	if version != rspec.Version {
		msgs = append(msgs, fmt.Sprintf("internal error: validate currently only handles version %s, but the supplied configuration targets %s", rspec.Version, version))
	}

	return
}

func (v *Validator) CheckPlatform() (msgs []string) {
	logrus.Debugf("check platform")

	validCombins := map[string][]string{
		"android":   {"arm"},
		"darwin":    {"386", "amd64", "arm", "arm64"},
		"dragonfly": {"amd64"},
		"freebsd":   {"386", "amd64", "arm"},
		"linux":     {"386", "amd64", "arm", "arm64", "ppc64", "ppc64le", "mips64", "mips64le", "s390x"},
		"netbsd":    {"386", "amd64", "arm"},
		"openbsd":   {"386", "amd64", "arm"},
		"plan9":     {"386", "amd64"},
		"solaris":   {"amd64"},
		"windows":   {"386", "amd64"}}
	platform := v.spec.Platform
	for os, archs := range validCombins {
		if os == platform.OS {
			for _, arch := range archs {
				if arch == platform.Arch {
					return nil
				}
			}
			msgs = append(msgs, fmt.Sprintf("Combination of %q and %q is invalid.", platform.OS, platform.Arch))
		}
	}
	msgs = append(msgs, fmt.Sprintf("Operation system %q of the bundle is not supported yet.", platform.OS))

	return
}

func (v *Validator) CheckHooks() (msgs []string) {
	logrus.Debugf("check hooks")

	msgs = append(msgs, checkEventHooks("pre-start", v.spec.Hooks.Prestart, v.HostSpecific)...)
	msgs = append(msgs, checkEventHooks("post-start", v.spec.Hooks.Poststart, v.HostSpecific)...)
	msgs = append(msgs, checkEventHooks("post-stop", v.spec.Hooks.Poststop, v.HostSpecific)...)

	return
}

func checkEventHooks(hookType string, hooks []rspec.Hook, hostSpecific bool) (msgs []string) {
	for _, hook := range hooks {
		if !filepath.IsAbs(hook.Path) {
			msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not absolute path", hookType, hook.Path))
		}

		if hostSpecific {
			fi, err := os.Stat(hook.Path)
			if err != nil {
				msgs = append(msgs, fmt.Sprintf("Cannot find %s hook: %v", hookType, hook.Path))
			}
			if fi.Mode()&0111 == 0 {
				msgs = append(msgs, fmt.Sprintf("The %s hook %v: is not executable", hookType, hook.Path))
			}
		}

		for _, env := range hook.Env {
			if !envValid(env) {
				msgs = append(msgs, fmt.Sprintf("Env %q for hook %v is in the invalid form.", env, hook.Path))
			}
		}
	}

	return
}

func (v *Validator) CheckProcess() (msgs []string) {
	logrus.Debugf("check process")

	process := v.spec.Process
	if !filepath.IsAbs(process.Cwd) {
		msgs = append(msgs, fmt.Sprintf("cwd %q is not an absolute path", process.Cwd))
	}

	for _, env := range process.Env {
		if !envValid(env) {
			msgs = append(msgs, fmt.Sprintf("env %q should be in the form of 'key=value'. The left hand side must consist solely of letters, digits, and underscores '_'.", env))
		}
	}

	if len(process.Args) == 0 {
		msgs = append(msgs, fmt.Sprintf("args must not be empty"))
	} else {
		if filepath.IsAbs(process.Args[0]) {
			var rootfsPath string
			if filepath.IsAbs(v.spec.Root.Path) {
				rootfsPath = v.spec.Root.Path
			} else {
				rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
			}
			absPath := filepath.Join(rootfsPath, process.Args[0])
			fileinfo, err := os.Stat(absPath)
			if os.IsNotExist(err) {
				logrus.Warnf("executable %q is not available in rootfs currently", process.Args[0])
			} else if err != nil {
				msgs = append(msgs, err.Error())
			} else {
				m := fileinfo.Mode()
				if m.IsDir() || m&0111 == 0 {
					msgs = append(msgs, fmt.Sprintf("arg %q is not executable", process.Args[0]))
				}
			}
		}
	}

	for index := 0; index < len(process.Capabilities); index++ {
		capability := process.Capabilities[index]
		if !capValid(capability) {
			msgs = append(msgs, fmt.Sprintf("capability %q is not valid, man capabilities(7)", process.Capabilities[index]))
		}
	}

	for index := 0; index < len(process.Rlimits); index++ {
		if !rlimitValid(process.Rlimits[index].Type) {
			msgs = append(msgs, fmt.Sprintf("rlimit type %q is invalid.", process.Rlimits[index].Type))
		}
		if process.Rlimits[index].Hard < process.Rlimits[index].Soft {
			msgs = append(msgs, fmt.Sprintf("hard limit of rlimit %s should not be less than soft limit.", process.Rlimits[index].Type))
		}
	}

	if len(process.ApparmorProfile) > 0 {
		profilePath := filepath.Join(v.bundlePath, v.spec.Root.Path, "/etc/apparmor.d", process.ApparmorProfile)
		_, err := os.Stat(profilePath)
		if err != nil {
			msgs = append(msgs, err.Error())
		}
	}

	return
}

func supportedMountTypes(OS string, hostSpecific bool) (map[string]bool, error) {
	supportedTypes := make(map[string]bool)

	if OS != "linux" && OS != "windows" {
		logrus.Warnf("%v is not supported to check mount type", OS)
		return nil, nil
	} else if OS == "windows" {
		supportedTypes["ntfs"] = true
		return supportedTypes, nil
	}

	if hostSpecific {
		f, err := os.Open("/proc/filesystems")
		if err != nil {
			return nil, err
		}
		defer f.Close()

		s := bufio.NewScanner(f)
		for s.Scan() {
			if err := s.Err(); err != nil {
				return supportedTypes, err
			}

			text := s.Text()
			parts := strings.Split(text, "\t")
			if len(parts) > 1 {
				supportedTypes[parts[1]] = true
			} else {
				supportedTypes[parts[0]] = true
			}
		}

		supportedTypes["bind"] = true

		return supportedTypes, nil
	}
	logrus.Warn("Checking linux mount types without --host-specific is not supported yet")
	return nil, nil
}

func (v *Validator) CheckMounts() (msgs []string) {
	logrus.Debugf("check mounts")

	supportedTypes, err := supportedMountTypes(v.spec.Platform.OS, v.HostSpecific)
	if err != nil {
		msgs = append(msgs, err.Error())
		return
	}

	if supportedTypes != nil {
		for _, mount := range v.spec.Mounts {
			if !supportedTypes[mount.Type] {
				msgs = append(msgs, fmt.Sprintf("Unsupported mount type %q", mount.Type))
			}

			if !filepath.IsAbs(mount.Destination) {
				msgs = append(msgs, fmt.Sprintf("destination %v is not an absolute path", mount.Destination))
			}
		}
	}

	return
}

//Linux only
func (v *Validator) CheckLinux() (msgs []string) {
	logrus.Debugf("check linux")

	utsExists := false
	ipcExists := false
	mountExists := false
	netExists := false
	userExists := false

	for index := 0; index < len(v.spec.Linux.Namespaces); index++ {
		if !namespaceValid(v.spec.Linux.Namespaces[index]) {
			msgs = append(msgs, fmt.Sprintf("namespace %v is invalid.", v.spec.Linux.Namespaces[index]))
		} else if len(v.spec.Linux.Namespaces[index].Path) == 0 {
			if v.spec.Linux.Namespaces[index].Type == rspec.UTSNamespace {
				utsExists = true
			} else if v.spec.Linux.Namespaces[index].Type == rspec.IPCNamespace {
				ipcExists = true
			} else if v.spec.Linux.Namespaces[index].Type == rspec.NetworkNamespace {
				netExists = true
			} else if v.spec.Linux.Namespaces[index].Type == rspec.MountNamespace {
				mountExists = true
			} else if v.spec.Linux.Namespaces[index].Type == rspec.UserNamespace {
				userExists = true
			}
		}
	}

	if (len(v.spec.Linux.UIDMappings) > 0 || len(v.spec.Linux.GIDMappings) > 0) && !userExists {
		msgs = append(msgs, "UID/GID mappings requires a new User namespace to be specified as well")
	} else if len(v.spec.Linux.UIDMappings) > 5 {
		msgs = append(msgs, "Only 5 UID mappings are allowed (linux kernel restriction).")
	} else if len(v.spec.Linux.GIDMappings) > 5 {
		msgs = append(msgs, "Only 5 GID mappings are allowed (linux kernel restriction).")
	}

	for k := range v.spec.Linux.Sysctl {
		if strings.HasPrefix(k, "net.") && !netExists {
			msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new Network namespace to be specified as well", k))
		}
		if strings.HasPrefix(k, "fs.mqueue.") {
			if !mountExists || !ipcExists {
				msgs = append(msgs, fmt.Sprintf("Sysctl %v requires a new IPC namespace and Mount namespace to be specified as well", k))
			}
		}
	}

	if v.spec.Platform.OS == "linux" && !utsExists && v.spec.Hostname != "" {
		msgs = append(msgs, fmt.Sprintf("On Linux, hostname requires a new UTS namespace to be specified as well"))
	}

	// Linux devices validation
	devList := make(map[string]bool)
	typeList := make(map[string]bool)
	for index := 0; index < len(v.spec.Linux.Devices); index++ {
		device := v.spec.Linux.Devices[index]
		if !deviceValid(device) {
			msgs = append(msgs, fmt.Sprintf("device %v is invalid.", device))
		}

		if _, exists := devList[device.Path]; exists {
			msgs = append(msgs, fmt.Sprintf("device %s is duplicated", device.Path))
		} else {
			var rootfsPath string
			if filepath.IsAbs(v.spec.Root.Path) {
				rootfsPath = v.spec.Root.Path
			} else {
				rootfsPath = filepath.Join(v.bundlePath, v.spec.Root.Path)
			}
			absPath := filepath.Join(rootfsPath, device.Path)
			fi, err := os.Stat(absPath)
			if os.IsNotExist(err) {
				devList[device.Path] = true
			} else if err != nil {
				msgs = append(msgs, err.Error())
			} else {
				fStat, ok := fi.Sys().(*syscall.Stat_t)
				if !ok {
					msgs = append(msgs, fmt.Sprintf("cannot determine state for device %s", device.Path))
					continue
				}
				var devType string
				switch fStat.Mode & syscall.S_IFMT {
				case syscall.S_IFCHR:
					devType = "c"
				case syscall.S_IFBLK:
					devType = "b"
				case syscall.S_IFIFO:
					devType = "p"
				default:
					devType = "unmatched"
				}
				if devType != device.Type || (devType == "c" && device.Type == "u") {
					msgs = append(msgs, fmt.Sprintf("unmatched %s already exists in filesystem", device.Path))
					continue
				}
				if devType != "p" {
					dev := fStat.Rdev
					major := (dev >> 8) & 0xfff
					minor := (dev & 0xff) | ((dev >> 12) & 0xfff00)
					if int64(major) != device.Major || int64(minor) != device.Minor {
						msgs = append(msgs, fmt.Sprintf("unmatched %s already exists in filesystem", device.Path))
						continue
					}
				}
				if device.FileMode != nil {
					expectedPerm := *device.FileMode & os.ModePerm
					actualPerm := fi.Mode() & os.ModePerm
					if expectedPerm != actualPerm {
						msgs = append(msgs, fmt.Sprintf("unmatched %s already exists in filesystem", device.Path))
						continue
					}
				}
				if device.UID != nil {
					if *device.UID != fStat.Uid {
						msgs = append(msgs, fmt.Sprintf("unmatched %s already exists in filesystem", device.Path))
						continue
					}
				}
				if device.GID != nil {
					if *device.GID != fStat.Gid {
						msgs = append(msgs, fmt.Sprintf("unmatched %s already exists in filesystem", device.Path))
						continue
					}
				}
			}
		}

		// unify u->c when comparing, they are synonyms
		var devID string
		if device.Type == "u" {
			devID = fmt.Sprintf("%s:%d:%d", "c", device.Major, device.Minor)
		} else {
			devID = fmt.Sprintf("%s:%d:%d", device.Type, device.Major, device.Minor)
		}

		if _, exists := typeList[devID]; exists {
			logrus.Warnf("type:%s, major:%d and minor:%d for linux devices is duplicated", device.Type, device.Major, device.Minor)
		} else {
			typeList[devID] = true
		}
	}

	if v.spec.Linux.Resources != nil {
		ms := v.CheckLinuxResources()
		msgs = append(msgs, ms...)
	}

	if v.spec.Linux.Seccomp != nil {
		ms := v.CheckSeccomp()
		msgs = append(msgs, ms...)
{'fraction_non_alphanumeric': 0.15847444927319046, 'fraction_numerical': 0.007118237674209501, 'mean_word_length': 3.6309854267869537, 'pattern_counts': {'":': 20, '<': 13, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1031115', 'n_tokens_mistral': 10805, 'n_tokens_neox': 9653, 'n_words': 2350}