#!/usr/bin/bash
persist_base=/etc/mdevctl.d
mdev_base=/sys/bus/mdev/devices
parent_base=/sys/class/mdev_bus
# Alias 'lsmdev' to 'mdevctl list'
if [ $(basename $0) == "lsmdev" ]; then
set -- "list" "${@}"
fi
# See https://stackoverflow.com/a/29754866/4775714 for getopt usage
getopt --test > /dev/null
if [ $? -ne 4 ]; then
echo "Sorry, $0 requires enhanced getopt support"
exit 1
fi
config={}
attrs=[]
jsonify() {
echo "\"$1\""
}
validate_attr() {
# first arg: expected root, second arg: attr to check
if [ ! -w "$1/$2" ]; then
echo "Attribute $2 cannot be set" >&2
return 1
fi
if [ $(realpath --relative-base "$1" "$1/$2") == "$2" ]; then
return 0
else
echo "Invalid attribute $2" >&2
return 1
fi
}
del_config_key() {
key="$1"
config=$(echo "$config" | jq -c -M --arg key "$key" 'del(."$key")')
}
set_config_key() {
key=$(jsonify "$1")
value=$(jsonify "$2")
del_config_key "$key"
config=$(echo "$config" | jq -c -M --argjson obj "{$key:$value}" '. + $obj')
}
has_config_key() {
key="$1"
if [ $(echo "$config" | jq -M --arg key "$key" 'has($key)') == "true" ]; then
return 0
else
return 1
fi
}
get_config_key() {
key="$1"
echo "$config" | jq -r -M --arg key "$key" '.[$key]'
}
config_file() {
uuid="$1"
parent="$2"
if [ -n "$parent" ]; then
if [ ! -e "$persist_base/$parent/$uuid" ]; then
echo "Config for $uuid on $parent does not exist, define it first?" >&2
return 1
fi
echo "$persist_base/$parent/$uuid"
else
count=$(find "$persist_base" -name "$uuid" -type f | wc -l)
if [ "$count" -eq 0 ]; then
echo "Config for $uuid does not exist, define it first?" >&2
return 1
elif [ "$count" -gt 1 ]; then
echo "Multiple configs found for $uuid, specify a parent" >&2
return 1
fi
echo $(find "$persist_base" -name "$uuid" -type f)
fi
return 0
}
get_attr_length() {
echo "$attrs" | jq -M '. | length'
}
get_attrs_raw() {
echo "$attrs"
}
add_attr_index() {
key=$(jsonify "$1")
value=$(jsonify "$2")
if [ -z "$3" ]; then
index=$(get_attr_length)
else
index="$3"
fi
attrs=$(echo "$attrs" | jq -c -M --argjson obj "{$key:$value}" \
--argjson i $index '.[0:$i] + [$obj] + .[$i:]')
}
del_attr_index() {
if [ -z "$1" ]; then
index=$(( $(get_attr_length) - 1 ))
else
index="$1"
fi
attrs=$(echo "$attrs" | jq -c -M --argjson i "$index" '.[0:$i] + .[$i+1:]')
}
get_attr_index_key() {
if [ -z "$1" ]; then
index=0
else
index="$1"
fi
echo "$attrs" | jq -r -M --argjson i "$index" '.[$i] | keys | .[]'
}
get_attr_index_value() {
if [ -z "$1" ]; then
index=0
else
index="$1"
fi
echo "$attrs" | jq -r -M --argjson i "$index" '.[$i] | .[]'
}
get_attr_index_raw() {
if [ -z "$1" ]; then
index=0
else
index="$1"
fi
echo "$attrs" | jq -c -M --argjson i "$index" '.[$i]'
}
read_config() {
file="$1"
config=$(jq -c -M '.' "$file")
if [ $? -eq 0 ] && has_config_key mdev_type && has_config_key start; then
attrs=$(echo "$config" | jq -c -M '.attrs')
if [ "$attrs" == null ]; then
attrs=[]
fi
config=$(echo "$config" | jq -c -M 'del(.attrs)')
return 0
else
config={}
attrs=[]
return 1
fi
}
dump_config() {
echo "$config" | jq -M --argjson attrs "{\"attrs\":$attrs}" '. + $attrs'
}
write_config() {
file="$1"
dump_config > "$file"
}
valid_uuid () {
uuid="$1"
if [[ "$uuid" =~ ^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$ ]]; then
echo "$uuid"
fi
}
create_mdev() {
uuid="$1"
parent="$2"
type="$3"
if [ -z "$(valid_uuid $uuid)" ]; then
echo "Invalid UUID $uuid" >&2
return 1
fi
if [ -L "$mdev_base/$uuid" ]; then
cur_parent=$(basename $(realpath "$mdev_base/$uuid" | sed -s "s/\/$uuid//"))
if [ $? -ne 0 ] || [ "$cur_parent" != "$parent" ]; then
echo "Device exists under different parent" >&2
return 1
fi
cur_type=$(basename $(realpath "$mdev_base/$uuid/mdev_type"))
if [ $? -ne 0 ] || [ "$cur_type" != "$type" ]; then
echo "Device exists with different type" >&2
return 1
fi
return 1
fi
if [ ! -d "$parent_base/$parent/mdev_supported_types" ]; then
echo "Parent $parent is not currently registered for mdev support" >&2
return 1
fi
if [ ! -d "$parent_base/$parent/mdev_supported_types/$type" ]; then
echo "Parent $parent does not support mdev type $type" >&2
return 1
fi
avail=$(cat "$parent_base/$parent/mdev_supported_types/$type/available_instances")
if [ $? -ne 0 ] || [ "$avail" -eq 0 ]; then
echo "No available instances of $type on $parent" >&2
return 1
fi
echo "$uuid" > "$parent_base/$parent/mdev_supported_types/$type/create"
if [ $? -ne 0 ]; then
echo "Error creating mdev type $type on $parent" >&2
return 1
fi
return 0
}
start_mdev() {
uuid="$1"
parent="$2"
type="$3"
if [ $# -eq 4 ]; then
print_uuid="$4"
fi
create_mdev "$uuid" "$parent" "$type"
if [ $? -eq 0 ]; then
count=$(( $(get_attr_length) - 1 ))
if [ "$count" -ge 0 ]; then
for i in $(seq 0 "$count"); do
attr=$(get_attr_index_key $i)
validate_attr "$mdev_base/$uuid" "$attr"
if [ $? -ne 0 ]; then
remove_mdev "$uuid"
return 1
fi
val=$(get_attr_index_value $i)
echo -e "$val" > "$mdev_base/$uuid/$attr"
if [ $? -ne 0 ]; then
echo "Failed to write $val to attribute $attr" >&2
remove_mdev "$uuid"
return 1
fi
done
fi
$print_uuid
return 0
fi
return 1
}
remove_mdev() {
uuid="$1"
if [ -z "$(valid_uuid $uuid)" ]; then
echo "Invalid UUID $uuid" >&2
return 1
fi
if [ ! -L "$mdev_base/$uuid" ]; then
return 1
fi
echo 1 > "$mdev_base/$uuid/remove"
if [ $? -ne 0 ]; then
echo "Error removing device $uuid" >&2
return 1
fi
return 0
}
# Get a UUID that's not locally defined or running
unique_uuid() {
count=1
while [ $count -ne 0 ]; do
uuid=$(uuidgen)
count=$(find "$persist_base" -name "$uuid" -type f | wc -l)
if [ "$count" -eq 0 ] && [ -L "$mdev_base/$uuid" ]; then
count=1
fi
done
echo "$uuid"
}
usage() {
cat >&2 <<EOF
Usage: $(basename $0) {COMMAND} [options...]
Available commands:
define Define a config for an mdev device. Options:
<-u|--uuid=UUID> [-a|--auto]
[-u|--uuid=UUID] <-p|--parent=PARENT> <-t|--type=TYPE> [-a|--auto]
[-u|--uuid=UUID] <-p|--parent=PARENT> <--jsonfile=FILE>
If the device specified by the UUID currently exists, parent
and type may be omitted to use the existing values. The auto
option marks the device to start on parent availability.
If defined via FILE then type, startup, and any attributes
are provided via the file. Running devices are unaffected
by this command.
undefine Undefine, or remove a config for an mdev device. Options:
<-u|--uuid=UUID> [-p|--parent=PARENT]
If a UUID exists for multiple parents, all will be removed
unless a parent is specified. Running devices are unaffected
by this command.
modify Modify the config for a defined mdev device. Options:
<-u|--uuid=UUID> [-p|--parent=PARENT] [-t|--type=TYPE] \\
[--addattr=ATTRIBUTE] [--delattr] [-i|--index=INDEX] [--value=VALUE] \\
[-a|--auto|-m|--manual]
The parent option further identifies a UUID if it is not
unique, the parent for a device cannot be modified via this
command, undefine and re-define should be used instead. An
ATTRIBUTE can be added or removed, which correlates to a
sysfs attribute under the created device. Unless an INDEX
value is provided, operations are performed at the end of
the attribute list. VALUE is to be specified in the format
that is accepted by the attribute. Upon device start, mdevctl
will go through each attribute in order, writing the value into
the corresponding sysfs attribute for the device. The startup
mode of the device can also be selected, auto or manual.
Running devices are unaffected by this command.
start Start an mdev device. Options:
<-u|--uuid=UUID> [-p|--parent=PARENT]
[-u|--uuid=UUID] <-p|--parent=PARENT> <-t|--type=TYPE>
[-u|--uuid=UUID] <-p|--parent=PARENT> <--jsonfile=FILE>
If the UUID is previously defined and unique, the UUID is
sufficient to start the device (UUIDs may not collide between
running devices). If a UUID is used in multiple defined
configs, the parent device is necessary to identify the config.
When specified with PARENT and TYPE, the device is fully
specified and will be started based only on these parameters.
The UUID is optional in this case, if not provided a UUID is
generated and returned as output. A FILE may replace the TYPE
specification and also include additional attributes to be
applied to the started device.
stop Stop an mdev device. Options:
<-u|--uuid=UUID>
list List mdev devices. Options:
[-d|--defined] [-u|--uuid=UUID] [-p|--parent=PARENT] \\
[--dumpjson] [-v|--verbose]
With no options, information about the currently running mdev
devices is provided. Specifying DEFINED lists the
configuration of defined devices, regardless of their running
state. This may be further reduced by specifying specific
UUID or PARENT devices to list. The dumpjson option provides
output listing in machine readable JSON format. When a UUID
option is provided and the result is a single device, the
output contains only the JSON fields necessary to recreate a
config file for the device (minus attributes for listings of
running devices). When the verbose option is provided, the
human readable listing will include attributes for the
device(s).
types List mdev types. Options:
[-p|--parent=PARENT] [--dumpjson]
Specifying a PARENT lists only the types provided by the given
parent device. The dumpjson option provides output in machine
readable JSON format.
EOF
exit 1
}
if [ $# -lt 1 ]; then
usage
fi
case ${1} in
#
# Internal commands, these are expected to be called from other scripts
# and therefore do not offer the convenience of getopt processing
# (they typically take one arg after the command) and are not listed in
# the usage text
#
start-parent-mdevs)
if [ $# -ne 2 ]; then
echo "Usage: $0 $1 <parent device>" >&2
exit 1
fi
parent="$2"
if [ ! -d "$persist_base/$parent" ]; then
# Nothing to do
exit 0
fi
for file in $(find "$persist_base/$parent/" -maxdepth 1 -mindepth 1 -type f); do
uuid=$(basename "$file")
if [ -n "$(valid_uuid $uuid)" ]; then
read_config "$file"
if [ $? -ne 0 ]; then
continue
fi
if [ "$(get_config_key start)" == "auto" ]; then
create_mdev "$uuid" "$parent" "$(get_config_key mdev_type)"
if [ $? -ne 0 ]; then
echo "Failed to create mdev $uuid, type $(get_config_key mdev_type) on $parent" >&2
# continue...
fi
fi
fi
done
exit 0
;;
#
# User commands
#
--help|-h|-?)
usage
;;
define)
cmd="$1"
OPTIONS="u:p:t:a"
LONGOPTS="uuid:,parent:,type:,auto,jsonfile:"
shift
;;
undefine)
cmd="$1"
OPTIONS="u:p:"
LONGOPTS="uuid:,parent:"
shift
;;
modify)
cmd="$1"
OPTIONS="u:p:t:ami:"
LONGOPTS="uuid:,parent:,type:,auto,manual,addattr:,delattr,index:,value:"
shift
;;
start)
cmd="$1"
OPTIONS="u:p:t:"
LONGOPTS="uuid:,parent:,type:,jsonfile:"
shift
;;
stop)
cmd="$1"
OPTIONS="u:"
LONGOPTS="uuid:"
shift
;;
list)
cmd="$1"
OPTIONS="du:p:v"
LONGOPTS="defined,uuid:,dumpjson,parent:,verbose"
shift
;;
types)
cmd="$1"
OPTIONS="p:"
LONGOPTS="parent:,dumpjson"
shift
;;
*)
echo "Unknown command $1" >&2
usage
;;
esac
PARSED=$(getopt --options="$OPTIONS" --longoptions="$LONGOPTS" --name "$(basename $0)" -- "$@")
if [ $? -ne 0 ]; then
exit 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-u|--uuid)
uuid="$2"
shift 2
;;
-p|--parent)
parent="$2"
shift 2
;;
-t|--type)
type="$2"
shift 2
;;
--jsonfile)
jsonfile="$2"
shift 2
;;
--addattr)
addattr="$2"
shift 2
;;
-i|--index)
index="$2"
shift 2
;;
--value)
value="$2"
shift 2
;;
--delattr)
delattr=y
shift 1
;;
--dumpjson)
dumpjson=y
shift
;;
-a|--auto)
auto=y
shift 1
;;
-m|--manual)
manual=y
shift 1
;;
-d|--defined)
defined=y
shift 1
;;
-a|--available)
available=y
shift 1
;;
-v|--verbose)
verbose=y
shift 1
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 1
;;
esac
done
if [ $# -ne 0 ]; then
echo "$(basename $0): Unknown options: $@"
exit 1
fi
case "$cmd" in
define)
if [ -n "$jsonfile" ]; then
if [ ! -r "$jsonfile" ]; then
echo "Unable to read file $jsonfile" >&2
exit 1
fi
if [ -n "$type" ]; then
echo "Device type cannot be specified separately from $jsonfile" >&2
exit 1
fi
if [ -z "$parent" ]; then
echo "Parent device required to define device via $jsonfile" >&2
exit 1
fi
if [ -z "$uuid" ]; then
uuid=$(unique_uuid)
print_uuid="echo $uuid"
fi
if [ -e "$persist_base/$parent/$uuid" ]; then
echo "Cowardly refusing to overwrite existing config for $parent/$uuid" >&2
exit 1
fi
set -o errexit
read_config "$jsonfile"
if [ $? -ne 0 ]; then
echo "Error reading $jsonfile" >&2
exit 1
fi
write_config "$persist_base/$parent/$uuid"
if [ $? -ne 0 ]; then
exit 1
fi
$print_uuid
exit 0
fi
if [ -n "$uuid" ]; then
if [ -z "$parent" ]; then
if [ ! -L "$mdev_base/$uuid" ] || [ -n "$type" ]; then
usage
fi
parent=$(basename $(realpath "$mdev_base/$uuid" | sed -s "s/\/$uuid//"))
type=$(basename $(realpath "$mdev_base/$uuid/mdev_type"))
fi
if [ -e "$persist_base/$parent/$uuid" ]; then
echo "Device $uuid on $parent already defined, try modify?" >&2
exit 1
fi
else
uuid=$(unique_uuid)
print_uuid="echo $uuid"
fi
if [ -z "$parent" ]; then
usage
fi
if [ -z "$type" ]; then
usage
fi
if [ -n "$auto" ]; then
start="auto"
else
start="manual"
fi
set -o errexit
mkdir -p "$persist_base/$parent"
set_config_key mdev_type "$type"
set_config_key start "$start"
write_config "$persist_base/$parent/$uuid"
if [ $? -eq 0 ]; then
$print_uuid
fi
;;
undefine)
if [ -z "$uuid" ]; then
usage
fi
set -o errexit
if [ -n "$parent" ]; then
rm -f "$persist_base/$parent/$uuid"
else
find "$persist_base" -name "$uuid" -type f | xargs rm -f
fi
;;
modify)
if [ -z "$uuid" ]; then
usage
fi
if [ -n "$auto" ] && [ -n "$manual" ]; then
echo "Options --auto and --manual are mutually exclusive" >&2
exit 1
fi
set -o errexit
file=$(config_file "$uuid" "$parent")
if [ $? -ne 0 ]; then
exit 1
fi
read_config "$file"
if [ $? -ne 0 ]; then
echo "Config file $file invalid" >&2
exit 1
fi
if [ -n "$type" ]; then
set_config_key mdev_type "$type"
fi
if [ -n "$auto" ]; then
set_config_key start auto
fi
if [ -n "$manual" ]; then
set_config_key start manual
fi
if [ -n "$addattr" ] && [ -n "$delattr" ]; then
usage
fi
if [ -n "$addattr" ]; then
if [ -n "$index" ]; then
if [ "$index" -eq "$index" ] 2>/dev/null; then
:
else
echo "Provided index is not a number" >&2
usage
fi
fi
if [ -z "$value" ]; then
echo "No attribute value provided" >&2
usage
fi
add_attr_index "$addattr" "$value" "$index"
fi
if [ -n "$delattr" ]; then
if [ -n "$index" ]; then
if [ "$index" -eq "$index" ] 2>/dev/null; then
:
else
echo "Provided index is not a number" >&2
usage
fi
fi
del_attr_index "$index"
fi
write_config "$file"
;;
start)
set -o errexit
if [ -n "$jsonfile" ]; then
if [ ! -r "$jsonfile" ]; then
echo "Unable to read file $jsonfile" >&2
exit 1
fi
if [ -n "$type" ]; then
echo "Device type cannot be specified separately from $jsonfile" >&2
exit 1
fi
if [ -z "$parent" ]; then
echo "Parent device required to start device via $jsonfile" >&2
exit 1
fi
if [ -z "$uuid" ]; then
uuid=$(unique_uuid)
print_uuid="echo $uuid"
fi
read_config "$jsonfile"
if [ $? -ne 0 ]; then
echo "Error reading $jsonfile" >&2
exit 1
fi
type="$(get_config_key mdev_type)"
start_mdev "$uuid" "$parent" "$type" "$print_uuid"
exit $?
fi
# We don't implement a placement policy
if [ -n "$type" ] && [ -z "$parent" ]; then
usage
fi
# The device is not fully specified without TYPE, we must find
# a config file, with optional PARENT for disambiguation
if [ -z "$type" ] && [ -n "$uuid" ]; then
count=$(find "$persist_base" -name "$uuid" -type f | wc -l)
if [ "$count" -eq 0 ]; then
echo "Config for $uuid does not exist, define it first?" >&2
exit 1
elif [ "$count" -gt 1 ]; then
if [ -z "$parent" ] || [ ! -e "$persist_base/$parent/$uuid" ]; then
echo "Multiple configs found for $uuid, specify a parent" >&2
exit 1
fi
file="$persist_base/$parent/$uuid"
else
file=$(find "$persist_base" -name "$uuid" -type f)
if [ -n "$parent" ]; then
cur_parent=$(basename $(echo "$file" | sed -s "s/\/$uuid//"))
if [ "$cur_parent" != "$parent" ]; then
echo "Config for $parent/$uuid does not exist, define it first?" >&2
exit 1
fi
fi
fi
read_config "$file"
if [ $? -ne 0 ]; then
echo "Config file $file invalid" >&2
exit 1
fi
if [ -z "$parent" ]; then
parent=$(basename $(echo "$file" | sed -s "s/\/$uuid//"))
fi
type="$(get_config_key mdev_type)"
fi
if [ -z "$uuid" ]; then
# Device must be full specified otherwise for generated UUID
if [ -z "$parent" ] || [ -z "$type" ]; then
echo "Device is insufficiently specified" >&2
usage
fi
uuid=$(unique_uuid)
print_uuid="echo $uuid"
fi
start_mdev "$uuid" "$parent" "$type" "$print_uuid"
exit $?
;;
stop)
if [ -z "$uuid" ]; then
usage
fi
set -o errexit
remove_mdev "$uuid"
;;
list)
json="[]"
txt=""
if [ -n "$defined" ]; then
for dir in $(find "$persist_base/" -maxdepth 1 -mindepth 1 -type d | sort); do
p=$(basename "$dir")
if [ -n "$parent" ] && [ "$parent" != "$p" ]; then
continue
fi
for mdev in $(find "$dir/" -maxdepth 1 -mindepth 1 -type f); do
u=$(basename "$mdev")
if [ -n "$uuid" ] && [ "$uuid" != "$u" ]; then
continue
fi
read_config "$mdev"
if [ $? -ne 0 ]; then
continue
fi
type="$(get_config_key mdev_type)"
start="$(get_config_key start)"
txt+="$u $p $type $start"
if [ -L "$mdev_base/$u" ]; then
cur_parent=$(basename $(realpath "$mdev_base/$u" | sed -s "s/\/$u//"))
if [ "$cur_parent" == "$p" ]; then
cur_type=$(basename $(realpath "$mdev_base/$u/mdev_type"))
if [ "$cur_type" == "$type" ]; then
txt+=" (active)"
fi
fi
fi
json_tmp="{\"$p\":[{\"$u\":{"\"mdev_type\":\"$type\"",\"start\":\"$start\""
txt+="\n"
if [ -n "$verbose" ] || [ -n "$dumpjson" ]; then
count=$(( $(get_attr_length) - 1 ))
if [ $count -ge 0 ]; then
json_tmp+=",\"attrs\":$(get_attrs_raw)"
txt+=" Attrs:\n"
for i in $(seq 0 "$count"); do
txt+=" @{$i}: $(get_attr_index_raw $i)\n"
done
fi
fi
json_tmp+="}}]}"
json=$(echo "$json" | jq -c -M --argjson obj "$json_tmp" '. + [$obj]')
done
done
else
if [ ! -d "$mdev_base" ]; then
exit 0
fi
for mdev in $(find "$mdev_base/" -maxdepth 1 -mindepth 1 -type l); do
u=$(basename "$mdev")
if [ -n "$uuid" ] && [ "$uuid" != "$u" ]; then
continue
fi
p=$(basename $(realpath "$mdev_base/$u" | sed -s "s/\/$u//"))
if [ -n "$parent" ] && [ "$parent" != "$p" ]; then
continue
fi
type=$(basename $(realpath "$mdev/mdev_type"))
json_tmp="{\"$p\":[{\"$u\":{\"mdev_type\":\"$type\"}}]}"
txt+="$u $p $type"
if [ -f "$persist_base/$p/$u" ]; then
read_config "$persist_base/$p/$u"
if [ $? -eq 0 ] && [ "$(get_config_key mdev_type)" == "$type" ]; then
txt+=" (defined)"
fi
fi
txt+="\n"
json=$(echo "$json" | jq -c -M --argjson obj "$json_tmp" '. + [$obj]')
done
fi
if [ -n "$dumpjson" ]; then
if [ $(echo "$json" | jq 'length') -gt 0 ]; then
# https://stackoverflow.com/a/43337323/4775714
json=$(echo "$json" | jq -c -M '[reduce .[] as $o ({}; reduce ($o|keys)[] as $key (.; .[$key] += $o[$key] ))]')
fi
# If specified to a single device, output such that it can be
# piped into a config file, else print entire hierarchy
if [ -n "$uuid" ] && [ $(echo "$json" | jq -M 'length') -eq 1 ] &&
[ $(echo "$json" | jq -M '.[] | length') -eq 1 ] &&
[ $(echo "$json" | jq -M '.[] | .[] | length') -eq 1 ]; then
key=$(echo "$json" | jq -r -M '.[] | .[] | .[] | keys | .[]')
key=$(jsonify $key)
echo "$json" | jq -M --argjson key "$key" '.[] | .[] | .[] | .[$key]'
else
echo "$json" | jq -M '.'
fi
else
echo -en "$txt"
fi
;;
types)
if [ ! -d "$parent_base" ]; then
if [ -n "$dumpjson" ]; then
echo "[]" | jq -M '.'
fi
exit 0
fi
json="[]"
txt=""
for dir in $(find "$parent_base/" -maxdepth 1 -mindepth 1 -type l | sort); do
p=$(basename "$dir")
if [ -n "$parent" ] && [ "$parent" != "$p" ]; then
continue
fi
txt+="$p\n"
for parent_type in $(find "$dir/mdev_supported_types/" -maxdepth 1 -mindepth 1 -type d | sort); do
type=$(basename "$parent_type")
txt+=" $type\n"
avail=$(cat "$parent_type/available_instances")
txt+=" Available instances: $avail\n"
api=$(cat "$parent_type/device_api")
txt+=" Device API: $api\n"
json_tmp="{\"$p\":[{\"$type\":{\"available_instances\":$avail,\"device_api\":\"$api\""
if [ -e "$parent_type/name" ]; then
name=$(cat "$parent_type/name")
json_tmp+=",\"name\":\"$name\""
txt+=" Name: $name\n"
fi
if [ -e "$parent_type/description" ]; then
descr=$(cat "$parent_type/description" | sed -e ':a;N;$!ba;s/\n/, /g')
json_tmp+=",\"description\":\"$descr\""
txt+=" Description: $descr\n"
fi
json_tmp+="}}]}"
json=$(echo "$json" | jq -c -M --argjson obj "$json_tmp" '. + [$obj]')
done
done
if [ -n "$dumpjson" ]; then
if [ $(echo "$json" | jq 'length') -gt 0 ]; then
# https://stackoverflow.com/a/43337323/4775714
json=$(echo "$json" | jq -c -M '[reduce .[] as $o ({}; reduce ($o|keys)[] as $key (.; .[$key] += $o[$key] ))]')
fi
echo "$json" | jq -M '.'
else
echo -en "$txt"
fi
;;
esac