summaryrefslogtreecommitdiff
path: root/tools/ds-identify
diff options
context:
space:
mode:
Diffstat (limited to 'tools/ds-identify')
-rwxr-xr-xtools/ds-identify1015
1 files changed, 1015 insertions, 0 deletions
diff --git a/tools/ds-identify b/tools/ds-identify
new file mode 100755
index 00000000..203eac0d
--- /dev/null
+++ b/tools/ds-identify
@@ -0,0 +1,1015 @@
+#!/bin/sh
+#
+# ds-identify is configured via /etc/cloud/ds-identify.cfg
+# or on the kernel command line. It takes primarily 2 inputs:
+# datasource: can specify the datasource that should be used.
+# kernel command line option: ci.datasource=<dsname>
+#
+# policy: a string that indicates how ds-identify should operate.
+# kernel command line option: ci.di.policy=<policy>
+# default setting is:
+# search,found=all,maybe=all,notfound=disable
+
+# report: write config to /run/cloud-init/cloud.cfg.report (instead of
+# /run/cloud-init/cloud.cfg, which effectively makes this dry-run).
+# enable: do nothing
+# ds-identify writes no config and just exits success
+# the caller (cloud-init-generator) then enables cloud-init to run
+# just without any aid from ds-identify.
+# disable: disable cloud-init
+#
+# [report,]found=value,maybe=value,notfound=value
+# found: (default=first)
+# first: use the first found do no further checking
+# all: enable all DS_FOUND
+#
+# maybe: (default=all)
+# if nothing returned 'found', then how to handle maybe.
+# no network sources are allowed to return 'maybe'.
+# all: enable all DS_MAYBE
+# none: ignore any DS_MAYBE
+#
+# notfound: (default=disable)
+# disable: disable cloud-init
+# enable: enable cloud-init
+#
+#
+# zesty:
+# policy: found=first,maybe=all,none=disable
+# xenial:
+# policy: found=all,maybe=all,none=enable
+# and then at a later date
+
+
+set -u
+set -f
+UNAVAILABLE="unavailable"
+CR="
+"
+ERROR="error"
+DI_ENABLED="enabled"
+DI_DISABLED="disabled"
+
+DI_DEBUG_LEVEL="${DEBUG_LEVEL:-1}"
+
+PATH_ROOT=${PATH_ROOT:-""}
+PATH_RUN=${PATH_RUN:-"${PATH_ROOT}/run"}
+PATH_SYS_CLASS_DMI_ID=${PATH_SYS_CLASS_DMI_ID:-${PATH_ROOT}/sys/class/dmi/id}
+PATH_SYS_HYPERVISOR=${PATH_SYS_HYPERVISOR:-${PATH_ROOT}/sys/hypervisor}
+PATH_SYS_CLASS_BLOCK=${PATH_SYS_CLASS_BLOCK:-${PATH_ROOT}/sys/class/block}
+PATH_DEV_DISK="${PATH_DEV_DISK:-${PATH_ROOT}/dev/disk}"
+PATH_VAR_LIB_CLOUD="${PATH_VAR_LIB_CLOUD:-${PATH_ROOT}/var/lib/cloud}"
+PATH_DI_CONFIG="${PATH_DI_CONFIG:-${PATH_ROOT}/etc/cloud/ds-identify.cfg}"
+PATH_PROC_CMDLINE="${PATH_PROC_CMDLINE:-${PATH_ROOT}/proc/cmdline}"
+PATH_PROC_1_CMDLINE="${PATH_PROC_1_CMDLINE:-${PATH_ROOT}/proc/1/cmdline}"
+PATH_CLOUD_CONFD="${PATH_CLOUD_CONFD:-${PATH_ROOT}/etc/cloud}"
+PATH_RUN_CI="${PATH_RUN_CI:-${PATH_RUN}/cloud-init}"
+PATH_RUN_CI_CFG=${PATH_RUN_CI_CFG:-${PATH_RUN_CI}/cloud.cfg}
+
+DI_LOG="${DI_LOG:-${PATH_RUN_CI}/ds-identify.log}"
+_DI_LOGGED=""
+
+# set DI_MAIN='noop' in environment to source this file with no main called.
+DI_MAIN=${DI_MAIN:-main}
+
+DI_DEFAULT_POLICY="search,found=all,maybe=all,notfound=${DI_DISABLED}"
+DI_DEFAULT_POLICY_NO_DMI="search,found=all,maybe=all,notfound=${DI_ENABLED}"
+DI_DMI_PRODUCT_NAME=""
+DI_DMI_SYS_VENDOR=""
+DI_DMI_PRODUCT_SERIAL=""
+DI_DMI_PRODUCT_UUID=""
+DI_FS_LABELS=""
+DI_KERNEL_CMDLINE=""
+DI_VIRT=""
+
+DI_UNAME_KERNEL_NAME=""
+DI_UNAME_KERNEL_RELEASE=""
+DI_UNAME_KERNEL_VERSION=""
+DI_UNAME_MACHINE=""
+DI_UNAME_NODENAME=""
+DI_UNAME_OPERATING_SYSTEM=""
+DI_UNAME_CMD_OUT=""
+
+DS_FOUND=0
+DS_NOT_FOUND=1
+DS_MAYBE=2
+
+DI_DSNAME=""
+# this has to match the builtin list in cloud-init, it is what will
+# be searched if there is no setting found in config.
+DI_DSLIST_DEFAULT="MAAS ConfigDrive NoCloud AltCloud Azure Bigstep \
+CloudSigma CloudStack DigitalOcean Ec2 OpenNebula OpenStack OVF SmartOS"
+DI_DSLIST=""
+DI_MODE=""
+DI_REPORT=""
+DI_ON_FOUND=""
+DI_ON_MAYBE=""
+DI_ON_NOTFOUND=""
+
+
+error() {
+ set -- "ERROR:" "$@";
+ debug 0 "$@"
+ stderr "$@"
+}
+warn() {
+ set -- "WARN:" "$@"
+ debug 0 "$@"
+ stderr "$@"
+}
+
+stderr() { echo "$@" 1>&2; }
+
+debug() {
+ local lvl="$1"
+ shift
+ [ "$lvl" -gt "${DI_DEBUG_LEVEL}" ] && return
+
+ if [ "$_DI_LOGGED" != "$DI_LOG" ]; then
+ # first time here, open file descriptor for append
+ case "$DI_LOG" in
+ stderr) :;;
+ ?*/*)
+ if [ ! -d "${DI_LOG%/*}" ]; then
+ mkdir -p "${DI_LOG%/*}" || {
+ stderr "ERROR:" "cannot write to $DI_LOG"
+ DI_LOG="stderr"
+ }
+ fi
+ esac
+ if [ "$DI_LOG" = "stderr" ]; then
+ exec 3>&2
+ else
+ ( exec 3>>"$DI_LOG" ) && exec 3>>"$DI_LOG" || {
+ stderr "ERROR: failed writing to $DI_LOG. logging to stderr.";
+ exec 3>&2
+ DI_LOG="stderr"
+ }
+ fi
+ _DI_LOGGED="$DI_LOG"
+ fi
+ echo "$@" 1>&3
+}
+
+get_dmi_field() {
+ local path="${PATH_SYS_CLASS_DMI_ID}/$1"
+ if [ ! -f "$path" ] || [ ! -r "$path" ]; then
+ _RET="$UNAVAILABLE"
+ return
+ fi
+ read _RET < "${path}" || _RET="$ERROR"
+}
+
+block_dev_with_label() {
+ local p="${PATH_DEV_DISK}/by-label/$1"
+ [ -b "$p" ] || return 1
+ _RET=$p
+ return 0
+}
+
+read_fs_labels() {
+ cached "${DI_FS_LABELS}" && return 0
+ # do not rely on links in /dev/disk which might not be present yet.
+ # note that older blkid versions do not report DEVNAME in 'export' output.
+ local out="" ret=0 oifs="$IFS" line="" delim=","
+ local labels=""
+ if is_container; then
+ # blkid will in a container, or at least currently in lxd
+ # not provide useful information.
+ DI_FS_LABELS="$UNAVAILABLE:container"
+ else
+ out=$(blkid -c /dev/null -o export) || {
+ ret=$?
+ error "failed running [$ret]: blkid -c /dev/null -o export"
+ return $ret
+ }
+ IFS="$CR"
+ set -- $out
+ IFS="$oifs"
+ for line in "$@"; do
+ case "${line}" in
+ LABEL=*) labels="${labels}${line#LABEL=}${delim}";;
+ esac
+ done
+ DI_FS_LABELS="${labels%${delim}}"
+ fi
+}
+
+cached() {
+ [ -n "$1" ] && _RET="$1" && return || return 1
+}
+
+
+has_cdrom() {
+ [ -e "${PATH_ROOT}/dev/cdrom" ]
+}
+
+read_virt() {
+ cached "$DI_VIRT" && return 0
+ local out="" r="" virt="${UNAVAILABLE}"
+ if [ -d /run/systemd ]; then
+ out=$(systemd-detect-virt 2>&1)
+ r=$?
+ if [ $r -eq 0 ] || { [ $r -ne 0 ] && [ "$out" = "none" ]; }; then
+ virt="$out"
+ fi
+ fi
+ DI_VIRT=$virt
+}
+
+is_container() {
+ case "${DI_VIRT}" in
+ lxc|lxc-libvirt|systemd-nspawn|docker|rkt) return 0;;
+ *) return 1;;
+ esac
+}
+
+read_kernel_cmdline() {
+ cached "${DI_KERNEL_CMDLINE}" && return
+ local cmdline="" fpath="${PATH_PROC_CMDLINE}"
+ if is_container; then
+ local p1path="${PATH_PROC_1_CMDLINE}" x=""
+ cmdline="${UNAVAILABLE}:container"
+ if [ -f "$p1path" ] && x=$(tr '\0' ' ' < "$p1path"); then
+ cmdline=$x
+ fi
+ elif [ -f "$fpath" ]; then
+ read cmdline <"$fpath"
+ else
+ cmdline="${UNAVAILABLE}:no-cmdline"
+ fi
+ DI_KERNEL_CMDLINE="$cmdline"
+}
+
+read_dmi_sys_vendor() {
+ cached "${DI_DMI_SYS_VENDOR}" && return
+ get_dmi_field sys_vendor
+ DI_DMI_SYS_VENDOR="$_RET"
+}
+
+read_dmi_product_name() {
+ cached "${DI_DMI_PRODUCT_NAME}" && return
+ get_dmi_field product_name
+ DI_DMI_PRODUCT_NAME="$_RET"
+}
+
+read_dmi_product_uuid() {
+ cached "${DI_DMI_PRODUCT_UUID}" && return
+ get_dmi_field product_uuid
+ DI_DMI_PRODUCT_UUID="$_RET"
+}
+
+read_dmi_product_serial() {
+ cached "${DI_DMI_PRODUCT_SERIAL}" && return
+ get_dmi_field product_serial
+ DI_DMI_PRODUCT_SERIAL="$_RET"
+}
+
+read_uname_info() {
+ # run uname, and parse output.
+ # uname is tricky to parse as it outputs always in a given order
+ # independent of option order. kernel-version is known to have spaces.
+ # 1 -s kernel-name
+ # 2 -n nodename
+ # 3 -r kernel-release
+ # 4.. -v kernel-version(whitespace)
+ # N-2 -m machine
+ # N-1 -o operating-system
+ cached "${DI_UNAME_CMD_OUT}" && return
+ local out="${1:-}" ret=0 buf=""
+ if [ -z "$out" ]; then
+ out=$(uname -snrvmo) || {
+ ret=$?
+ error "failed reading uname with 'uname -snrvmo'"
+ return $ret
+ }
+ fi
+ set -- $out
+ DI_UNAME_KERNEL_NAME="$1"
+ DI_UNAME_NODENAME="$2"
+ DI_UNAME_KERNEL_RELEASE="$3"
+ shift 3
+ while [ $# -gt 2 ]; do
+ buf="$buf $1"
+ shift
+ done
+ DI_UNAME_KERNEL_VERSION="${buf# }"
+ DI_UNAME_MACHINE="$1"
+ DI_UNAME_OPERATING_SYSTEM="$2"
+ DI_UNAME_CMD_OUT="$out"
+ return 0
+}
+
+parse_yaml_array() {
+ # parse a yaml single line array value ([1,2,3], not key: [1,2,3]).
+ # supported with or without leading and closing brackets
+ # ['1'] or [1]
+ # '1', '2'
+ local val="$1" oifs="$IFS" ret="" tok=""
+ val=${val#[}
+ val=${val%]}
+ IFS=","; set -- $val; IFS="$oifs"
+ for tok in "$@"; do
+ trim "$tok"
+ unquote "$_RET"
+ ret="${ret} $_RET"
+ done
+ _RET="${ret# }"
+}
+
+read_datasource_list() {
+ cached "$DI_DSLIST" && return
+ local dslist=""
+ # if DI_DSNAME is set directly, then avoid parsing config.
+ if [ -n "${DI_DSNAME}" ]; then
+ dslist="${DI_DSNAME}"
+ fi
+
+ # LP: #1582323. cc:{'datasource_list': ['name']}
+ # more generically cc:<yaml>[end_cc]
+ local cb="]" ob="["
+ case "$DI_KERNEL_CMDLINE" in
+ *cc:*datasource_list*)
+ t=${DI_KERNEL_CMDLINE##*datasource_list}
+ t=${t%%$cb*}
+ t=${t##*$ob}
+ parse_yaml_array "$t"
+ dslist=${_RET}
+ ;;
+ esac
+ if [ -z "$dslist" ] && check_config datasource_list; then
+ debug 1 "$_RET_fname set datasource_list: $_RET"
+ parse_yaml_array "$_RET"
+ dslist=${_RET}
+ fi
+ if [ -z "$dslist" ]; then
+ dslist=${DI_DSLIST_DEFAULT}
+ debug 1 "no datasource_list found, using default:" $dslist
+ fi
+ DI_DSLIST=$dslist
+ return 0
+}
+
+dmi_product_name_matches() {
+ is_container && return 1
+ case "${DI_DMI_PRODUCT_NAME}" in
+ $1) return 0;;
+ esac
+ return 1
+}
+
+dmi_product_name_is() {
+ is_container && return 1
+ [ "${DI_DMI_PRODUCT_NAME}" = "$1" ]
+}
+
+dmi_sys_vendor_is() {
+ is_container && return 1
+ [ "${DI_DMI_SYS_VENDOR}" = "$1" ]
+}
+
+has_fs_with_label() {
+ local label=",$1,"
+ case "${DI_FS_LABELS}" in
+ *,$label,*) return 0;;
+ esac
+ return 1
+}
+
+nocase_equal() {
+ # nocase_equal(a, b)
+ # return 0 if case insenstive comparision a.lower() == b.lower()
+ # different lengths
+ [ "${#1}" = "${#2}" ] || return 1
+ # case sensitive equal
+ [ "$1" = "$2" ] && return 0
+
+ local delim="-delim-"
+ out=$(echo "$1${delim}$2" | tr A-Z a-z)
+ [ "${out#*${delim}}" = "${out%${delim}*}" ]
+}
+
+check_seed_dir() {
+ # check_seed_dir(name, [required])
+ # check the seed dir /var/lib/cloud/seed/<name> for 'required'
+ # required defaults to 'meta-data'
+ local name="$1"
+ local dir="${PATH_VAR_LIB_CLOUD}/seed/$name"
+ [ -d "$dir" ] || return 1
+ shift
+ if [ $# -eq 0 ]; then
+ set -- meta-data
+ fi
+ local f=""
+ for f in "$@"; do
+ [ -f "$dir/$f" ] || return 1
+ done
+ return 0
+}
+
+probe_floppy() {
+ cached "${STATE_FLOPPY_PROBED}" && return "${STATE_FLOPPY_PROBED}"
+ local fpath=/dev/floppy
+
+ [ -b "$fpath" ] ||
+ { STATE_FLOPPY_PROBED=1; return 1; }
+
+ modprobe --use-blacklist floppy >/dev/null 2>&1 ||
+ { STATE_FLOPPY_PROBED=1; return 1; }
+
+ udevadm settle "--exit-if-exists=$fpath" ||
+ { STATE_FLOPPY_PROBED=1; return 1; }
+
+ [ -b "$fpath" ]
+ STATE_FLOPPY_PROBED=$?
+ return "${STATE_FLOPPY_PROBED}"
+}
+
+
+dscheck_CloudStack() {
+ is_container && return ${DS_NOT_FOUND}
+ dmi_product_name_matches "CloudStack*" && return $DS_FOUND
+ return $DS_NOT_FOUND
+}
+
+dscheck_CloudSigma() {
+ # http://paste.ubuntu.com/23624795/
+ dmi_product_name_is "CloudSigma" && return $DS_FOUND
+ return $DS_NOT_FOUND
+}
+
+check_config() {
+ # somewhat hackily read config for 'key' in files matching 'files'
+ # currently does not respect any hierarchy.
+ local key="$1" files="" bp="${PATH_CLOUD_CONFD}/cloud.cfg"
+ if [ $# -eq 1 ]; then
+ files="$bp ${bp}.d/*.cfg"
+ else
+ files="$*"
+ fi
+ shift
+ set +f; set -- $files; set +f;
+ if [ "$1" = "$files" -a ! -f "$1" ]; then
+ return 1
+ fi
+ local fname="" line="" ret="" found=0 found_fn=""
+ for fname in "$@"; do
+ [ -f "$fname" ] || continue
+ while read line; do
+ line=${line%%#*}
+ case "$line" in
+ $key:\ *|$key:)
+ ret=${line#*:};
+ ret=${ret# };
+ found=$((found+1))
+ found_fn="$fname";;
+ esac
+ done <"$fname"
+ done
+ if [ $found -ne 0 ]; then
+ _RET="$ret"
+ _RET_fname="$found_fn"
+ return 0
+ fi
+ return 1
+}
+
+dscheck_MAAS() {
+ is_container && return "${DS_NOT_FOUND}"
+ # heuristic check for ephemeral boot environment
+ # for maas that do not set 'ci.dsname=' in the ephemeral environment
+ # these have iscsi root and cloud-config-url on the cmdline.
+ local maasiqn="iqn.2004-05.com.ubuntu:maas"
+ case "${DI_KERNEL_CMDLINE}" in
+ *cloud-config-url=*${maasiqn}*|*${maasiqn}*cloud-config-url=*)
+ return ${DS_FOUND}
+ ;;
+ esac
+
+ # check config files written by maas for installed system.
+ local confd="${PATH_CLOUD_CONFD}"
+ local fnmatch="$confd/*maas*.cfg $confd/*kernel_cmdline*.cfg"
+ if check_config "MAAS" "$fnmatch"; then
+ return "${DS_FOUND}"
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_NoCloud() {
+ local fslabel="cidata" d=""
+ for d in nocloud nocloud-net; do
+ check_seed_dir "$d" meta-data user-data && return ${DS_FOUND}
+ done
+ if has_fs_with_label "${fslabel}"; then
+ return ${DS_FOUND}
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+check_configdrive_v2() {
+ if has_fs_with_label "config-2"; then
+ return ${DS_FOUND}
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+check_configdrive_v1() {
+ # FIXME: this has to check any file system that is vfat...
+ # for now, just return not found.
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_ConfigDrive() {
+ local ret=""
+ check_configdrive_v2
+ ret=$?
+ [ $DS_FOUND -eq $ret ] && return $ret
+
+ check_configdrive_v1
+}
+
+dscheck_DigitalOcean() {
+ dmi_sys_vendor_is DigitalOcean && return ${DS_FOUND}
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_OpenNebula() {
+ check_seed_dir opennebula && return ${DS_FOUND}
+ has_fs_with_label "CONTEXT" && return ${DS_FOUND}
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_OVF() {
+ local p=""
+ check_seed_dir ovf ovf-env.xml && return "${DS_FOUND}"
+
+ has_cdrom || return ${DS_NOT_FOUND}
+
+ # FIXME: currently just return maybe if there is a cdrom
+ # ovf iso9660 transport does not specify an fs label.
+ # better would be to check if
+ return ${DS_MAYBE}
+}
+
+dscheck_Azure() {
+ # http://paste.ubuntu.com/23630873/
+ # $ grep /sr0 /run/blkid/blkid.tab
+ # <device DEVNO="0x0b00" TIME="1481737655.543841"
+ # UUID="112D211272645f72" LABEL="rd_rdfe_stable.161212-1209"
+ # TYPE="udf">/dev/sr0</device>
+ #
+ check_seed_dir azure ovf-env.xml && return ${DS_FOUND}
+
+ [ "${DI_VIRT}" = "microsoft" ] || return ${DS_NOT_FOUND}
+
+ has_fs_with_label "rd_rdfe_*" && return ${DS_FOUND}
+
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_Bigstep() {
+ # bigstep is activated by presense of seed file 'url'
+ check_seed_dir "bigstep" url && return ${DS_FOUND}
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_Ec2() {
+ # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/identify_ec2_instances.html
+ # http://paste.ubuntu.com/23630859/
+ local uuid="" hvuuid="$PATH_ROOT/sys/hypervisor/uuid"
+ is_container && return ${DS_NOT_FOUND}
+ # if the (basically) xen specific /sys/hypervisor/uuid starts with 'ec2'
+ if [ -r "$hvuuid" ] && read uuid < "$hvuuid" &&
+ [ "${uuid#ec2}" != "$uuid" ]; then
+ return ${DS_FOUND}
+ fi
+
+ # product uuid and product serial start with case insensitive
+ local uuid=${DI_DMI_PRODUCT_UUID} serial=${DI_DMI_PRODUCT_SERIAL}
+ case "$uuid:$serial" in
+ [Ee][Cc]2*:[Ee][Cc]2)
+ # both start with ec2, now check for case insenstive equal
+ nocase_equal "$uuid" "$serial" && return ${DS_FOUND};;
+ esac
+
+ # search through config files to check for platform
+ local f="" match="${PATH_CLOUD_CONFD}/*ec2*.cfg"
+ # look for the key 'platform' (datasource/ec2/look_alike/behavior)
+ if check_config platform "$match"; then
+ if [ "$platform" != "Unknown" ]; then
+ _RET="$name"
+ return "${DS_FOUND}"
+ fi
+ fi
+
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_GCE() {
+ if dmi_product_name_is "Google Compute Engine"; then
+ return ${DS_FOUND}
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_OpenStack() {
+ # the openstack metadata http service
+
+ # if there is a config drive, then do not check metadata
+ # FIXME: if config drive not in the search list, then we should not
+ # do this check.
+ check_configdrive_v2
+ if [ $? -eq ${DS_FOUND} ]; then
+ return ${DS_NOT_FOUND}
+ fi
+ if dmi_product_name_is "OpenStack Nova"; then
+ return ${DS_FOUND}
+ fi
+ case "${DI_VIRT}" in
+ lxc|lxc-libvirt)
+ # FIXME: This could be container on openstack (nova-lxd)
+ # or nova-libvirt-lxc
+ return ${DS_NOT_FOUND}
+ ;;
+ esac
+
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_AliYun() {
+ # aliyun is not enabled by default (LP: #1638931)
+ # so if we are here, it is because the datasource_list was
+ # set to include it. Thus, 'maybe'.
+ return $DS_MAYBE
+}
+
+dscheck_AltCloud() {
+ # ctype: either the dmi product name, or contents of
+ # /etc/sysconfig/cloud-info
+ # if ctype == "vsphere"
+ # device = device with label 'CDROM'
+ # elif ctype == "rhev"
+ # device = /dev/floppy
+ # then, filesystem on that device must have
+ # user-data.txt or deltacloud-user-data.txt
+ local ctype="" dev=""
+ local match_rhev="[Rr][Hh][Ee][Vv]"
+ local match_vsphere="[Vv][Ss][Pp][Hh][Ee][Rr][Ee]"
+ local cinfo="${PATH_ROOT}/etc/sysconfig/cloud-info"
+ if [ -f "$cinfo" ]; then
+ read ctype < "$cinfo"
+ else
+ ctype="${DI_DMI_PRODUCT_NAME}"
+ fi
+ case "$ctype" in
+ ${match_rhev})
+ probe_floppy || return ${DS_NOT_FOUND}
+ dev="/dev/floppy"
+ ;;
+ ${match_vsphere})
+ block_dev_with_label CDROM || return ${DS_NOT_FOUND}
+ dev="$_RET"
+ ;;
+ *) return ${DS_NOT_FOUND};;
+ esac
+
+ # FIXME: need to check $dev for user-data.txt or deltacloud-user-data.txt
+ : "$dev"
+ return $DS_MAYBE
+}
+
+dscheck_SmartOS() {
+ # joyent cloud has two virt types: kvm and container
+ # on kvm, product name on joyent public cloud shows 'SmartDC HVM'
+ # on the container platform, uname's version has: BrandZ virtual linux
+ local smartdc_kver="BrandZ virtual linux"
+ dmi_product_name_matches "SmartDC*" && return $DS_FOUND
+ if [ "${DI_UNAME_KERNEL_VERSION}" = "${smartdc_kver}" ] &&
+ [ "${DI_VIRT}" = "container-other" ]; then
+ return ${DS_FOUND}
+ fi
+ return ${DS_NOT_FOUND}
+}
+
+dscheck_None() {
+ return ${DS_NOT_FOUND}
+}
+
+collect_info() {
+ read_virt
+ read_kernel_cmdline
+ read_uname_info
+ read_config
+ read_datasource_list
+ read_dmi_sys_vendor
+ read_dmi_product_name
+ read_dmi_product_serial
+ read_dmi_product_uuid
+ read_fs_labels
+}
+
+print_info() {
+ collect_info
+ _print_info
+}
+
+_print_info() {
+ local n="" v="" vars=""
+ vars="DMI_PRODUCT_NAME DMI_SYS_VENDOR DMI_PRODUCT_SERIAL"
+ vars="$vars DMI_PRODUCT_UUID"
+ vars="$vars FS_LABELS KERNEL_CMDLINE VIRT"
+ vars="$vars UNAME_KERNEL_NAME UNAME_KERNEL_RELEASE UNAME_KERNEL_VERSION"
+ vars="$vars UNAME_MACHINE UNAME_NODENAME UNAME_OPERATING_SYSTEM"
+ vars="$vars DSNAME DSLIST"
+ vars="$vars MODE REPORT ON_FOUND ON_MAYBE ON_NOTFOUND"
+ for v in ${vars}; do
+ eval n='${DI_'"$v"'}'
+ echo "$v=$n"
+ done
+ echo "pid=$$ ppid=$PPID"
+ is_container && echo "is_container=true" || echo "is_container=false"
+}
+
+write_result() {
+ local runcfg="${PATH_RUN_CI_CFG}" ret="" line=""
+ if [ "$DI_REPORT" = "true" ]; then
+ runcfg="$runcfg.report"
+ fi
+ for line in "$@"; do
+ echo "$line"
+ done > "$runcfg"
+ ret=$?
+ [ $ret -eq 0 ] || {
+ error "failed to write to ${runcfg}"
+ return $ret
+ }
+ return 0
+}
+
+found() {
+ local list="" ds=""
+ # always we write the None datasource last.
+ for ds in "$@" None; do
+ list="${list:+${list}, }$ds"
+ done
+ write_result "datasource_list: [ $list ]"
+ return
+}
+
+trim() {
+ set -- $*
+ _RET="$*"
+}
+
+unquote() {
+ # remove quotes from quoted value
+ local quote='"' tick="'"
+ local val="$1"
+ case "$val" in
+ ${quote}*${quote}|${tick}*${tick})
+ val=${val#?}; val=${val%?};;
+ esac
+ _RET="$val"
+}
+
+_read_config() {
+ # reads config from stdin, modifies _rc scoped environment vars.
+ # rc_policy and _rc_dsname
+ local line="" hash="#" ckey="" key="" val=""
+ while read line; do
+ line=${line%%${hash}*}
+ key="${line%%:*}"
+
+ # no : in the line.
+ [ "$key" = "$line" ] && continue
+ trim "$key"
+ key=${_RET}
+
+ val="${line#*:}"
+ trim "$val"
+ unquote "${_RET}"
+ val=${_RET}
+ case "$key" in
+ datasource) _rc_dsname="$val";;
+ policy) _rc_policy="$val";;
+ esac
+ done
+}
+
+parse_warn() {
+ echo "WARN: invalid value '$2' for key '$1'. Using $1=$3." 1>&2
+}
+
+parse_def_policy() {
+ local _rc_mode="" _rc_report="" _rc_found="" _rc_maybe="" _rc_notfound=""
+ local ret=""
+ parse_policy "$@"
+ ret=$?
+ _def_mode=$_rc_mode
+ _def_report=$_rc_report
+ _def_found=$_rc_found
+ _def_maybe=$_rc_maybe
+ _def_notfound=$_rc_notfound
+ return $ret
+}
+
+parse_policy() {
+ # parse_policy(policy, default)
+ # parse a policy string. sets
+ # _rc_mode (enable|disable,search)
+ # _rc_report true|false
+ # _rc_found first|all
+ # _rc_maybe all|none
+ # _rc_notfound enable|disable
+ local def=""
+ case "$DI_UNAME_MACHINE" in
+ # these have dmi data
+ i?86|x86_64|aarch64) def=${DI_DEFAULT_POLICY};;
+ *) def=${DI_DEFAULT_POLICY_NO_DMI};;
+ esac
+ local policy="$1"
+ local _def_mode="" _def_report="" _def_found="" _def_maybe=""
+ local _def_notfound=""
+ if [ $# -eq 1 ] || [ "$2" != "-" ]; then
+ def=${2:-${def}}
+ parse_def_policy "$def" -
+ fi
+
+ local mode="" report="" found="" maybe="" notfound=""
+ local oifs="$IFS" tok="" val=""
+ IFS=","; set -- $policy; IFS="$oifs"
+ for tok in "$@"; do
+ val=${tok#*=}
+ case "$tok" in
+ report) report=true;;
+ $DI_ENABLED|$DI_DISABLED|search) mode=$tok;;
+ found=all|found=first) found=$val;;
+ maybe=all|maybe=none) maybe=$val;;
+ notfound=$DI_ENABLED|notfound=$DI_DISABLED) notfound=$val;;
+ found=*)
+ parse_warn found "$val" "${_def_found}"
+ found=${_def_found};;
+ maybe=*)
+ parse_warn maybe "$val" "${_def_maybe}"
+ maybe=${_def_maybe};;
+ notfound=*)
+ parse_warn notfound "$val" "${_def_notfound}"
+ notfound=${_def_notfound};;
+ esac
+ done
+ report=${report:-${_def_report:-false}}
+ _rc_report=${report}
+ _rc_mode=${mode:-${_def_mode}}
+ _rc_found=${found:-${_def_found}}
+ _rc_maybe=${maybe:-${_def_maybe}}
+ _rc_notfound=${notfound:-${_def_notfound}}
+}
+
+read_config() {
+ local config=${PATH_DI_CONFIG}
+ local _rc_dsname="" _rc_policy="" ret=""
+ if [ -f "$config" ]; then
+ _read_config < "$config"
+ ret=$?
+ elif [ -e "$config" ]; then
+ error "$config exists but is not a file!"
+ ret=1
+ fi
+ local tok="" key="" val=""
+ for tok in ${DI_KERNEL_CMDLINE}; do
+ key=${tok%%=*}
+ val=${tok#*=}
+ case "$key" in
+ ci.ds) _rc_dsname="$val";;
+ ci.datasource) _rc_dsname="$val";;
+ ci.di.policy) _rc_policy="$val";;
+ esac
+ done
+
+ local _rc_mode _rc_report _rc_found _rc_maybe _rc_notfound
+ parse_policy "${_rc_policy}"
+ debug 1 "policy loaded: mode=${_rc_mode} report=${_rc_report}" \
+ "found=${_rc_found} maybe=${_rc_maybe} notfound=${_rc_notfound}"
+ DI_MODE=${_rc_mode}
+ DI_REPORT=${_rc_report}
+ DI_ON_FOUND=${_rc_found}
+ DI_ON_MAYBE=${_rc_maybe}
+ DI_ON_NOTFOUND=${_rc_notfound}
+
+ DI_DSNAME="${_rc_dsname}"
+ return $ret
+}
+
+
+manual_clean_and_existing() {
+ [ -f "${PATH_VAR_LIB_CLOUD}/instance/manual-clean" ]
+}
+
+main() {
+ local dscheck="" ret_dis=1 ret_en=0
+ collect_info
+
+ if [ ! -e "$PATH_RUN_CI_CFG" ]; then
+ # the first time the generator is run.
+ _print_info >> "$DI_LOG"
+ fi
+
+ case "$DI_MODE" in
+ $DI_DISABLED)
+ debug 1 "mode=$DI_DISABLED. returning $ret_dis"
+ return $ret_dis
+ ;;
+ $DI_ENABLED)
+ debug 1 "mode=$DI_ENABLED. returning $ret_en"
+ return $ret_en;;
+ search) :;;
+ esac
+
+ if [ -n "${DI_DSNAME}" ]; then
+ debug 1 "datasource '$DI_DSNAME' specified."
+ found "$DI_DSNAME"
+ return
+ fi
+
+ if manual_clean_and_existing; then
+ debug 1 "manual_cache_clean enabled. Not writing datasource_list."
+ write_result "# manual_cache_clean."
+ return
+ fi
+
+ # if there is only a single entry in $DI_DSLIST
+ set -- $DI_DSLIST
+ if [ $# -eq 1 ] || [ $# -eq 2 -a "$2" = "None" ] ; then
+ debug 1 "single entry in datasource_list ($DI_DSLIST) use that."
+ found "$@"
+ return
+ fi
+
+ local found="" ret="" ds="" maybe=""
+ for ds in ${DI_DSLIST}; do
+ dscheck_fn="dscheck_${ds}"
+ debug 2 "Checking for datasource '$ds' via '$dscheck_fn'"
+ if ! type "$dscheck_fn" >/dev/null 2>&1; then
+ warn "No check method '$dscheck_fn' for datasource '$ds'"
+ continue
+ fi
+ $dscheck_fn
+ ret="$?"
+ case "$ret" in
+ $DS_FOUND)
+ debug 1 "check for '$ds' returned found";
+ found="${found} $ds";;
+ $DS_MAYBE)
+ debug 1 "check for $ds returned maybe";
+ maybe="${maybe} $ds";;
+ *) debug 2 "check for $ds returned not-found[$ret]";;
+ esac
+ done
+
+ debug 2 "found=$found maybe=$maybe"
+ set -- $found
+ if [ $# -ne 0 ]; then
+ if [ $# -eq 1 ]; then
+ debug 1 "Found single datasource: $1"
+ else
+ # found=all
+ debug 1 "Found $# datasources found=${DI_ON_FOUND}: $*"
+ if [ "${DI_ON_FOUND}" = "first" ]; then
+ set -- "$1"
+ fi
+ fi
+ found "$@"
+ return
+ fi
+
+ set -- $maybe
+ if [ $# -ne 0 -a "${DI_ON_MAYBE}" != "none" ]; then
+ debug 1 "$# datasources returned maybe: $*"
+ found "$@"
+ return
+ fi
+
+ case "$DI_ON_NOTFOUND" in
+ $DI_DISABLED)
+ debug 1 "No result. notfound=$DI_DISABLED. returning $ret_dis."
+ return $ret_dis
+ ;;
+ $DI_ENABLED)
+ debug 1 "notfound=$DI_ENABLED. returning $ret_en"
+ return $ret_en;;
+ esac
+
+ error "Unexpected result"
+ return 3
+}
+
+noop() {
+ :
+}
+
+case "${DI_MAIN}" in
+ main|print_info|noop) "${DI_MAIN}" "$@";;
+ *) error "unexpected value for DI_MAIN"; exit 1;;
+esac
+
+# vi: syntax=sh ts=4 expandtab