#!/bin/sh -e
#
# 2021 Dennis Camera (cdist at dtnr.ch)
#
# This file is part of cdist.
#
# cdist is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cdist is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with cdist. If not, see <http://www.gnu.org/licenses/>.
#
# This explorer tries to determine what type of machine the target to be
# configured is (container, virtual machine, bare-metal).
#
# It will print one line for each layer it can detect.
# The format of all lines is: TYPE[ VERB VENDOR]
#
# VERB does not have a special meaning, it is just for better readability.
#
# e.g.
# container
# container on lxc
# virtual by kvm-spapr
#
# The third word of each line (except the first) can be composed of different
# parts concatenated with a `-' (minus) character, with each component being
# a specification of the previous, e.g.:
#  - lxc-libvirt             (LXC container, managed by libvirt)
#  - lpar-s390 / lpar-power  (LPAR running on IBM S/390 or POWER, respectively)
#  - xen-hvm / xen-pv        (Xen HVM vs para-virtualization)
#
# If this explorer cannot collect enough information about virtualization it
# will fall back to 'physical'.
#

# Add /sbin and /usr/sbin to the path so we can find system
# binaries like dmidecode.
PATH=$(getconf PATH 2>/dev/null) || PATH='/usr/bin:/bin'
PATH="/sbin:/usr/sbin:${PATH}"
export PATH

arch=$(uname -m | sed -e 's/i.86/i386/' -e 's/arm.*/arm/')
uname_s=$(uname -s)


is_command() { command -v "$1" >/dev/null 2>&1; }

files_same() {
	# shellcheck disable=SC2012
	LC_ALL=C df -P "$1" "$2" 2>/dev/null | {
		read -r _  # skip header line
		read -r fs1 _ _ _ _ mp1
		read -r fs2 _ _ _ _ mp2
		test "${fs1}" = "${fs2}" -a "${mp1}" = "${mp2}" || return 1
	} &&
	ls -1Ldi "$1" "$2" 2>/dev/null | {
		read -r ino1 _
		read -r ino2 _
		test "${ino1}" = "${ino2}" || return 1
	}
}

is_oneof() (
	x=$1; shift
	for y
	do
		test "${x}" = "${y}" || continue
		return 0
	done
	return 1
)

tolower() { LC_ALL=C tr '[:upper:]' '[:lower:]'; }

# shellcheck disable=SC2086
glob_exists() { set -- $1; test -e "$1"; }

get_dmi_field() {
	if is_oneof "${uname_s}" NetBSD
	then
		case $1
		in
			(system-manufacturer) _mib=machdep.dmi.system-vendor ;;
			(system-product-name) _mib=machdep.dmi.system-product ;;
			(system-version|system-uuid) _mib=machdep.dmi.$1 ;;
			(bios-vendor|bios-version) _mib=machdep.dmi.$1 ;;
			(biod-release-date) _mib=machdep.dmi.bios-date ;;
			(*) _mib= ;;
		esac

		test -n "${_mib}" && get_sysctl "${_mib}" | grep -e . && return
	fi

	if is_command dmidecode
	then
		dmidecode -s "$1"
	elif test -d "${dmi_sysfs-}"
	then
		case $1
		in
			(system-manufacturer) _filename=sys_vendor ;;
			(system-product-name) _filename=product_name ;;
			(*) _filename=$(echo "$1" | tr - _) ;;
		esac
		if test -r "${dmi_sysfs-}/${_filename}"
		then
			cat "${dmi_sysfs}/${_filename}"
		fi
		unset _filename
	elif test "${uname_s}" = OpenBSD
	then
		# NOTE: something similar to system-manufacutrer and system-product-name
		#       is available on OpenBSD in sysctl
		case $1
		in
			(system-manufacturer) _mib=hw.vendor ;;
			(system-product-name) _mib=hw.product ;;
			(*) _mib= ;;
		esac

		test -n "${_mib}" && get_sysctl "${_mib}" | grep -e . && return
	fi

	return 1
}

has_cpuinfo() { test -e /proc/cpuinfo; }

get_sysctl() {
	is_command sysctl && sysctl -n "$1" 2>/dev/null
}

detected_layer() {
	test -n "${_toplayer:-}" || echo "${_toplayer:=${1:?}}"
}


# Check for chroot

has_chroot_systemd() {
	is_command systemd-detect-virt && systemd-detect-virt --help | grep -q -e '^  -r'
}

check_chroot_systemd() {
	systemd-detect-virt -r
}

has_chroot_debian_ischroot() {
	is_command ischroot
}

check_chroot_debian_ischroot() {
	ischroot --default-false
}

has_chroot_procfs() {
	test -d /proc/
}

check_chroot_procfs() (
	is_chroot=false  # default
	if test -e /proc/1/root && ! files_same /proc/1/root /
	then
		is_chroot=true
	fi
	if test -e /proc/1/mountinfo -a -e /proc/self/mountinfo
	then
		has_mountinfo=true
		cmp -s /proc/1/mountinfo /proc/self/mountinfo || is_chroot=true
	fi

	if ${is_chroot}
	then
		# try to determine where the chroot has been mounted
		rootdev=$(LC_ALL=C df -P / | awk 'NR==2{print $1}')

		if test -e "${rootdev}"
		then
			# escape chroot to determine where the device containing the
			# chroot's / is mounted
			rootdevmnt=$(LC_ALL=C chroot /proc/1/root df -P "${rootdev}" | awk 'NR==2{print $6}')

			# shellcheck disable=SC2012
			root_ino=$(ls -1di / | awk '{print $1}')

			# escape chroot and find mount point by inode
			chroot /proc/1/root find "${rootdevmnt}" -xdev -type d -inum "${root_ino}"
		elif ${has_mountinfo}
		then
			while read -r mntid _ _ _ cmntpnt _
			do
				read -r _ _ _ _ hmntpnt _ <<-EOF
				$(grep -e "^$((mntid)) " /proc/1/mountinfo)
				EOF
				printf '%s\n' "${hmntpnt%${cmntpnt}}"
			done </proc/self/mountinfo \
			| sort -u \
			| head -n 1  # just take the first...
		fi

		return 0
	else
		return 1
	fi
)

# Check for container

has_ct_systemd() {
	is_command systemd-detect-virt && systemd-detect-virt --help | grep -q -e '^  -c'
}

check_ct_systemd() (
	_ctengine=$(systemd-detect-virt -c 2>/dev/null) &&
	case ${_ctengine}
	in
		(''|'none')
			return 1 ;;
		('container-other')
			return 0 ;;
		('systemd-nspawn')
			echo systemd_nspawn ;;
		(*)
			echo "${_ctengine}" ;;
	esac
)

has_ct_pid_1() {
	test -r /run/systemd/container -o -r /proc/1/environ
}

translate_container_name() {
	case $1
	in
		('lxc')
			echo lxc ;;
		('lxc-libvirt')
			echo lxc-libvirt ;;
		('podman')
			echo podman ;;
		('systemd-nspawn')
			echo systemd_nspawn ;;
		(*)
			return 1 ;;
	esac
	return 0
}

check_ct_pid_1() {
	if test -r /run/systemd/container
	then
		translate_container_name "$(head -n1 /run/systemd/container)" \
		&& return 0
	fi

	if test -r /proc/1/environ
	then
		translate_container_name "$(
			LC_ALL=C tr '\000' '\n' </proc/1/environ \
			| sed -n -e 's/^container=//p')" \
		&& return 0
	fi

	return 1
}

has_ct_cgroup() {
	test -r /proc/self/cgroup
}

check_ct_cgroup() {
	if grep -q -e ':/docker/' /proc/self/cgroup
	then
		echo docker
	elif grep -q -e ':/lxc/.*\.libvirt-lxc$' /proc/self/cgroup
	then
		echo libvirt-lxc
	elif grep -q -F '/libpod-' /proc/self/cgroup
	then
		echo podman
	else
		return 1
	fi
}

check_ct_files() {
	if test -e /.dockerenv -o -e /run/.dockerenv -o -e /.dockerinit
	then
		echo docker
	elif test -f /run/.containerenv
	then
		# https://github.com/containers/podman/issues/3586#issuecomment-661918679
		echo podman
	elif test -d /proc/vz -a ! -d /proc/bc -a -f /proc/user_beancounters \
		|| test -d /.cpt_hardlink_dir_*/
	then
		# Virtuozzo / OpenVZ
		#
		# /proc/vz: always exists if OpenVZ kernel is running
		# /proc/bc: exists only on node (not inside container)
		# /proc/user_beancounters - exists inside container
		#
		# /.cpt_hardlink_dir_*/ may be (?) created by vzctl:
		# https://github.com/kolyshkin/vzctl/commit/09e974f

		echo openvz
	elif grep -qie 'Microsoft\|WSL' /proc/sys/kernel/osrelease /proc/version 2>/dev/null
	then
		# https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
		echo wsl
	elif test -d /var/.cagefs
	then
		# https://docs.cloudlinux.com/cloudlinux_os_components/#cagefs
		# CageFS is not "really" a container, but it isn't a chroot either.
		echo cagefs
	elif test -e /proc/self/status && grep -q -e '^VxID: [0-9]\{1,\}' /proc/self/status
	then
		# Linux-VServer
		if grep -q -x -F 'VxID: 0' /proc/self/status
		then
			# host
			return 1
		else
			# guest
			echo linux_vserver
		fi
	else
		return 1
	fi
}

check_ct_os_specific() (
	if jailed=$(get_sysctl security.jail.jailed) && test "${jailed}" = 1
	then
		# FreeBSD jail
		echo jail
		return 0
	fi

	if is_command zonename && test "$(zonename)" != global
	then
		# Solaris zone
		echo zone
		return 0
	fi

	return 1
)


# Check for hypervisor

guess_hypervisor_from_cpu_model() {
	case $1
	in
		(*\ KVM\ *)
			echo kvm ;;
		(*\ QEMU\ *|QEMU\ *)
			echo qemu ;;
		(*)
			return 1 ;;
	esac
}

has_vm_systemd() {
	is_command systemd-detect-virt && systemd-detect-virt --help | grep -q -e '^  -v'
}

check_vm_systemd() (
	_hypervisor=$(systemd-detect-virt -v 2>/dev/null) &&
	case ${_hypervisor}
	in
		(''|'none')
			return 1 ;;
		('amazon')
			echo aws ;;
		('bochs')
			echo kvm ;;
		('microsoft')
			# assumption
			echo hyperv ;;
		('oracle')
			echo virtualbox ;;
		('vm-other')
			return 0 ;;
		(*)
			echo "${_hypervisor}" ;;
	esac
)

has_vm_cpuinfo() { has_cpuinfo; }

check_vm_cpuinfo() {
	if grep -q -F 'User Mode Linux' /proc/cpuinfo \
		|| grep -q -F 'UML' /proc/cpuinfo
	then
		# User Mode Linux
		echo uml
	elif grep -q -e '^vendor_id.*: PowerVM Lx86' /proc/cpuinfo
	then
		# IBM PowerVM Lx86 (Linux/x86 emulator)
		echo powervm_lx86
	elif grep -q -e '^vendor_id.*: IBM/S390' /proc/cpuinfo
	then
		# IBM SystemZ (S/390)
		if test -f /proc/sysinfo
		then
			if grep -q -e '^VM[0-9]* Control Program: KVM/Linux' /proc/sysinfo
			then
				echo kvm-s390
				return 0
			elif grep -q -e '^VM[0-9]* Control Program: z/VM' /proc/sysinfo
			then
				echo zvm
				return 0
			elif grep -q -e '^LPAR ' /proc/sysinfo
			then
				echo zvm-lpar
				return 0
			fi
		fi
		return 1
	else
		if grep -q -e '^model name.*:' /proc/cpuinfo
		then
			sed -n -e 's/^model name[^:]*: *//p' /proc/cpuinfo \
			| while read -r _cpu_model
			  do
				  guess_hypervisor_from_cpu_model "${_cpu_model}"
			  done \
			| sort \
			| uniq -c \
			| awk '
			  { if ($1 > most_c) { most_c = $1; most_s = $2 } }
			  END {
				if (most_s) print most_s
				exit !most_s
			  }' \
			&& return 0
		fi
		return 1
	fi
}

check_vm_arch_specific() {
	case ${arch}
	in
		(ppc64|ppc64le)
			# Check PPC64 LPAR, KVM

			# example /proc/cpuinfo line indicating 'not baremetal'
			# platform	: pSeries
			#
			# example /proc/ppc64/lparcfg systemtype line
			# system_type=IBM pSeries (emulated by qemu)

			if has_cpuinfo && grep -q -e 'platform.**pSeries' /proc/cpuinfo
			then
				if test -e /proc/ppc64/lparcfg
				then
					# Assume LPAR, now detect shared or dedicated
					if grep -q -x -F 'shared_processor_mode=1' /proc/ppc64/lparcfg
					then
						echo powervm-shared
						return 0
					else
						echo powervm-dedicated
						return 0
					fi
				fi
			fi
			;;
		(sparc*)
			# Check for SPARC LDoms

			if test -e /dev/mdesc
			then
				if test -d /sys/class/vlds/ctrl -a -d /sys/class/vlds/sp
				then
					# control LDom
					return 1
				else
					# guest LDom
					echo ldom-sparc
				fi

				# MDPROP=/usr/lib/ldoms/mdprop.py
				# if test -x "${MDPROP}"
				# then
				# 	if test -n "$("${MDPROP}" -v iodevice device-type=pciex)"
				# 	then
				# 		echo ldoms-root
				# 		echo ldoms-io
				# 	elif test -n "$("${MDPROP}" -v iov-device vf-id=0)"
				# 	then
				# 		echo ldoms-io
				# 	fi
				# fi
				return 0
			fi
			;;
		(i?86|x86*|amd64|i86pc)
			# VMM CPUID flag denotes that this system is running under a VMM
			if is_oneof "${uname_s}" Darwin
			then
				get_sysctl machdep.cpu.features | tr ' ' '\n' | grep -qixF VMM \
				&& return 0
			fi
			if has_cpuinfo \
				&& grep -q -i -e '^flags.*:.*\(hypervisor\|vmm\)\( \|$\)' /proc/cpuinfo
			then
				return 0
			fi
			;;
		(ia64)
			if test -d /sys/bus/xen -a ! -d /sys/bus/xen-backend
			then
				# PV-on-HVM drivers installed in a Xen guest
				echo xen-hvm
				return 0
			fi
			;;
	esac
	return 1
}

has_vm_dmi() {
	# Check for various products in SMBIOS/DMI.
	# Note that DMI doesn't exist on all architectures (only x86 and some ARM).
	# On other architectures the $dmi variable will be empty.

	if test -d /sys/class/dmi/id/
	then
		dmi_sysfs=/sys/class/dmi/id
	elif test -d /sys/devices/virtual/dmi/id/
	then
		dmi_sysfs=/sys/devices/virtual/dmi/id
	fi

	# shellcheck disable=SC2015
	{
		is_command dmidecode \
		&& (
			# dmidecode needs to exit 0 and not print the No SMBIOS/DMI line
			dmi_out=$(dmidecode 2>&1) \
			&& ! printf '%s\n' "${dmi_out}" \
				 | grep -qF 'No SMBIOS nor DMI entry point found, sorry.'
			) \
		|| test -d "${dmi_sysfs}"
	}
}

check_vm_dmi() {
	case $(get_dmi_field system-product-name)
	in
		(*.metal)
			if test "$(get_dmi_field system-manufacturer)" = 'Amazon EC2'
			then
				# AWS EC2 bare metal -> no virtualisation
				return 1
			fi
			;;
		('BHYVE')
			echo bhyve
			return 0
			;;
		('Google Compute Engine')
			echo gce
			return 0
			;;
		('RHEV Hypervisor')
			# Red Hat Enterprise Virtualization
			echo rhev
			return 0
			;;
		('KVM'|'Bochs'|'KVM Virtual Machine')
			echo kvm
			return 0
			;;
		('Parallels Virtual Platform')
			echo parallels
			return 0
			;;
		('VirtualBox')
			echo virtualbox
			return 0
			;;
		('VMware Virtual Platform')
			echo vmware
			return 0
			;;
	esac

	case $(get_dmi_field system-manufacturer)
	in
		('Alibaba'*)
			case $(get_dmi_field system-product-name)
			in
				('Alibaba Cloud ECS')
					echo alibaba-ecs
					;;
				(*)
					echo alibaba
					;;
			esac
			return 0
			;;
		('Amazon EC2')
			# AWS on bare-metal or KVM
			echo aws-ec2
			return 0
			;;
		('innotek GmbH'|'Oracle Corporation')
			echo virtualbox
			return 0
			;;
		('Joyent')
			if test "$(get_dmi_field system-product-name)" = 'SmartDC HVM'
			then
				# SmartOS KVM
				echo kvm-smartdc_hvm
				return 0
			fi
			;;
		('Microsoft Corporation'*)
			if test "$(get_dmi_field system-product-name)" = 'Virtual Machine'
			then
				if test -e /proc/irq/7/hyperv \
				|| expr "$(get_dmi_field bios-version)" : 'VRTUAL.*' >/dev/null
				then
					echo hyperv
					return 0
				fi

				case $(get_dmi_field system-version)
				in
					(VPC[0-9]*|VS2005*|*[Vv]irtual*[Pp][Cc]*)
						echo virtualpc
						return 0
						;;
					(*)
						echo hyperv
						return 0
						;;
				esac
			fi
			;;
		('Nutanix')
			# Nutanix AHV. Similar to KVM.
			if test "$(get_dmi_field system-product-name)" = 'AHV'
			then
				echo nutanix_ahv
				return 0
			fi
			;;
		('oVirt')
			echo ovirt
			return 0
			;;
		('Parallels Software International Inc.')
			echo parallels
			return 0
			;;
		('QEMU')
			echo qemu
			return 0
			;;
		('VMware, Inc.')
			echo vmware
			return 0
			;;
	esac

	case $(get_dmi_field bios-vendor)
	in
		('Amazon EC2')
			# AWS on bare-metal or KVM
			echo aws-ec2
			return 0
			;;
		('BHYVE')
			echo bhyve
			return 0
			;;
		('innotek GmbH')
			echo virtualbox
			return 0
			;;
		('Parallels Software International Inc.')
			echo parallels
			return 0
			;;
		('Xen')
			if get_dmi_field bios-version | grep -q -e '\([0-9]\{1,\}\.\)\{2\}amazon'
			then
				# AWS on Xen
				echo aws-xen
				return 0
			fi
			;;
	esac

	return 1
}

check_vm_hyp_specific() {
	if is_command vmware-checkvm && vmware-checkvm >/dev/null
	then
		# vmware-checkvm is provided by VMware's open-vm-tools
		echo vmware
		return 0
	elif test -d /proc/xen
	then
		test -r /proc/xen/capabilities &&
		if grep -q -F 'control_d' /proc/xen/capabilities 2>/dev/null
		then
			# Xen dom0
			return 1
		else
			# Xen domU
			echo xen
			return 0
		fi
	fi
	return 1
}

has_vm_dt() {
	# OpenFirmware/Das U-Boot device-tree
	test -d /proc/device-tree
}

check_vm_dt() {
	case ${arch}
	in
		(arm|aarch64)
			if test -r /proc/device-tree/hypervisor/compatible
			then
				if grep -q -F 'xen' /proc/device-tree/hypervisor/compatible
				then
					echo xen
					return 0
				elif grep -q -F 'vmware' /proc/device-tree/hypervisor/compatible
				then
					# e.g. VMware ESXi on ARM
					echo vmware
					return 0
				fi
			fi
			if glob_exists /proc/device-tree/fw-cfg@*/compatible
			then
				# qemu,fw-cfg-mmio
				sed -e 's/,.*$//' /proc/device-tree/fw-cfg@*/compatible | head -n1
				return 0
			fi
			if grep -q -F 'dummy-virt' /proc/device-tree/compatible
			then
				echo lkvm
				return 0
			fi
			;;
		(ppc64*)
			if test -d /proc/device-tree/hypervisor \
				&& grep -qF 'linux,kvm' /proc/device-tree/hypervisor/compatible
			then
				# We are running as a spapr KVM guest on ppc64
				echo kvm-spapr
				return 0
			fi
			if test -r /proc/device-tree/ibm,partition-name \
				&& test -r /proc/device-tree/hmc-managed\? \
				&& test -r /proc/device-tree/chosen/qemu,graphic-width
			then
				echo powervm
			fi
			;;
	esac
	return 1
}

has_vm_sys_hypervisor() {
	test -d /sys/hypervisor/
}

check_vm_sys_hypervisor() {
	test -r /sys/hypervisor/type &&
	case $(head -n1 /sys/hypervisor/type)
	in
		(xen)
			# Ordinary kernel with pv_ops.	There does not seem to be
			# enough information at present to tell whether this is dom0
			# or domU.
			echo xen
			return 0
			;;
	esac
	return 1
}

check_vm_os_specific() {
	_hyp_generic=false

	case ${uname_s}
	in
		(Darwin)
			if hv_vmm_present=$(get_sysctl kern.hv_vmm_present) \
				&& test "${hv_vmm_present}" -ne 0
			then
				_hyp_generic=true
			fi
			;;
		(FreeBSD)
			# FreeBSD does not have /proc/cpuinfo even when procfs is used.
			# Instead there is a sysctl kern.vm_guest.
			# Which is 'none' if physical, else the virtualisation.
			vm_guest=$(get_sysctl kern.vm_guest | tolower) &&
			case ${vm_guest}
			in
				(none) ;;
				(generic) _hyp_generic=true ;;
				(*)
					# kernel could detect hypervisor
					case ${vm_guest}
					in
						(hv) echo hyperv ;;
						(vbox) echo virtualbox ;;
						(*) echo "${vm_guest}" ;;
					esac
					return 0
					;;
			esac
			;;
		(NetBSD)
			machdep_hv=$(get_sysctl machdep.hypervisor | tolower) &&
			case ${machdep_hv}
			in
				(none) ;;
				(generic) _hyp_generic=true ;;
				(*)
					# kernel could detect hypervisor
					case ${machdep_hv}
					in
						(hyper-v) echo hyperv ;;
						(xenhvm*) echo xen-hvm ;;
						(xenpv*) echo xen-pv ;;
						(xen*) echo xen ;;
						(*) echo "${machdep_hv}" ;;
					esac
					return 0
					;;
			esac
			;;
		(OpenBSD)
			if is_command hostctl && glob_exists /dev/pvbus[0-9]*
			then
				for _pvbus in /dev/pvbus[0-9]*
				do
					_h_out=$(hostctl -f "${_pvbus}" -t 2>/dev/null) || continue
					case $(expr "${_h_out}" : '[^:]*: *\(.*\)$')
					in
						(KVM) echo kvm ;;
						(Hyper-V) echo hyperv ;;
						(VMware) echo vmware ;;
						(Xen) echo xen ;;
						(bhyve) echo bhyve ;;
						(OpenBSD) echo openbsd_vmm ;;
					esac
					return 0
				done
			fi
			;;
		(SunOS)
			diag_conf=$(prtdiag | sed -n -e 's/.*Configuration: *//p' -e '/^$/q')
			# NOTE: Don't use -e or -F in Solaris grep
			if printf '%s\n' "${diag_conf}" | grep -q -i QEMU
			then
				echo qemu
				return 0
			elif printf '%s\n' "${diag_conf}" | grep -q -i VMware
			then
				echo vmware
				return 0
			fi
			;;
		(Linux)
			if is_command dmesg
			then
				while read -r line
				do
					case ${line}
					in
						('Booting paravirtualized kernel on ')
							case $(expr "${line}" : '.* kernel on \(.*\)')
							in
								('Xen')
									echo xen-pv; return 0 ;;
								('bare hardware')
									return 1 ;;
							esac
							;;
						('Hypervisor detected')
							case $(expr "${line}" : '.*: *\(.*\)')
							in
								('ACRN')
									echo acrn ;;
								('Jailhouse')
									echo jailhouse ;;
								('KVM')
									echo kvm ;;
								('Microsoft Hyper-V')
									echo hyperv ;;
								('VMware')
									echo vmware ;;
								('Xen HVM')
									echo xen-hvm ;;
								('Xen PV')
									echo xen-pv ;;
							esac
							return 0
							;;
						(lpar:*' under hypervisor')
							return 0 ;;
					esac
				done <<-EOF
				$(dmesg 2>/dev/null | awk '
				/Booting paravirtualized kernel on /
				/Hypervisor detected: /
				/lpar: .* under hypervisor/
				')
				EOF
			fi
	esac

	# Try to guess hypervisor based on CPU model (sysctl hw.model if available)
	if cpu_model=$(get_sysctl hw.model)
	then
		guess_hypervisor_from_cpu_model "${cpu_model}" && return 0
	fi

	if ${_hyp_generic}
	then
		# cannot say which hypervisor, but one was detected
		return 0
	else
		return 1
	fi
}

run_stage() {
	if type "has_$1_$2" >/dev/null 2>&1
	then
		"has_$1_$2"
	else
		true
	fi \
	&& "check_$1_$2"
}


# Execute chroot stages

for stage in \
	procfs debian_ischroot systemd
do
	chrootpnt=$(run_stage chroot ${stage}) || continue
	is_chrooted=true
	detected_layer 'chroot'
	if test -n "${chrootpnt}"
	then
		echo chroot at "${chrootpnt}"
		break
	fi
done
if ${is_chrooted:-false} && test -z "${chrootpnt}"
then
	# could determine chroot, but not its mount point
	echo chroot
fi


# Execute container stages

for stage in \
	systemd pid_1 cgroup files os_specific
do
	ctengine=$(run_stage ct ${stage}) || continue
	detected_layer 'container'
	is_contained=true
	if test -n "${ctengine}"
	then
		echo container on "${ctengine}"
		break
	fi
done
if ${is_contained:-false} && test -z "${ctengine}"
then
	# none of the stages could determine the specific container engine, but
	# we are running in some container.
	echo container
fi


# Execute virtual machine / hypervisor stages

for stage in \
	systemd os_specific hyp_specific sys_hypervisor dt dmi cpuinfo arch_specific
do
	hypervisor=$(run_stage vm ${stage}) || continue
	detected_layer 'virtual machine'
	is_virtual=true
	if test -n "${hypervisor}"
	then
		echo virtual by "${hypervisor}"
		break
	fi
done
if ${is_virtual:-false} && test -z "${hypervisor}"
then
	# none of the stages could determine the specific hypervisor, but
	# we are virtual.
	echo virtual
fi


# Fallback

detected_layer physical
