#!/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 . # # 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() { if test -e /proc/1/root && ! files_same /proc/1/root / 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}') # Get mount point chroot /proc/1/root find "${rootdevmnt}" -xdev -type d -inum "${root_ino}" fi ) return 0 fi return 1 } # 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' /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