xref: /linux/tools/sound/dapm-graph (revision f09d694cf799d27d6de25f04f3fd5ba9190631e1)
1e7bb4389SLuca Ceresoli#!/bin/sh
2e7bb4389SLuca Ceresoli# SPDX-License-Identifier: GPL-2.0
3e7bb4389SLuca Ceresoli#
4e7bb4389SLuca Ceresoli# Generate a graph of the current DAPM state for an audio card
5e7bb4389SLuca Ceresoli#
6e7bb4389SLuca Ceresoli# Copyright 2024 Bootlin
7e7bb4389SLuca Ceresoli# Author: Luca Ceresoli <luca.ceresol@bootlin.com>
8e7bb4389SLuca Ceresoli
9e7bb4389SLuca Ceresoliset -eu
10e7bb4389SLuca Ceresoli
1164a1e3ddSLuca CeresoliSTYLE_COMPONENT_ON="color=dodgerblue;style=bold"
1264a1e3ddSLuca CeresoliSTYLE_COMPONENT_OFF="color=gray40;style=filled;fillcolor=gray90"
13*d31babd7SNicolas FrattaroliSTYLE_NODE_ON="shape=box,style=bold,color=green4,fillcolor=white"
14e7bb4389SLuca CeresoliSTYLE_NODE_OFF="shape=box,style=filled,color=gray30,fillcolor=gray95"
15e7bb4389SLuca Ceresoli
16e7bb4389SLuca Ceresoli# Print usage and exit
17e7bb4389SLuca Ceresoli#
18e7bb4389SLuca Ceresoli# $1 = exit return value
19e7bb4389SLuca Ceresoli# $2 = error string (required if $1 != 0)
20e7bb4389SLuca Ceresoliusage()
21e7bb4389SLuca Ceresoli{
22e7bb4389SLuca Ceresoli    if [  "${1}" -ne 0 ]; then
23e7bb4389SLuca Ceresoli	echo "${2}" >&2
24e7bb4389SLuca Ceresoli    fi
25e7bb4389SLuca Ceresoli
26e7bb4389SLuca Ceresoli    echo "
27e7bb4389SLuca CeresoliGenerate a graph of the current DAPM state for an audio card.
28e7bb4389SLuca Ceresoli
29e7bb4389SLuca CeresoliThe DAPM state can be obtained via debugfs for a card on the local host or
30e7bb4389SLuca Ceresolia remote target, or from a local copy of the debugfs tree for the card.
31e7bb4389SLuca Ceresoli
32e7bb4389SLuca CeresoliUsage:
33e7bb4389SLuca Ceresoli    $(basename $0) [options] -c CARD                  - Local sound card
34e7bb4389SLuca Ceresoli    $(basename $0) [options] -c CARD -r REMOTE_TARGET - Card on remote system
35e7bb4389SLuca Ceresoli    $(basename $0) [options] -d STATE_DIR             - Local directory
36e7bb4389SLuca Ceresoli
37e7bb4389SLuca CeresoliOptions:
38e7bb4389SLuca Ceresoli    -c CARD             Sound card to get DAPM state of
39e7bb4389SLuca Ceresoli    -r REMOTE_TARGET    Get DAPM state from REMOTE_TARGET via SSH and SCP
40e7bb4389SLuca Ceresoli                        instead of using a local sound card
41e7bb4389SLuca Ceresoli    -d STATE_DIR        Get DAPM state from a local copy of a debugfs tree
42e7bb4389SLuca Ceresoli    -o OUT_FILE         Output file (default: dapm.dot)
43e7bb4389SLuca Ceresoli    -D                  Show verbose debugging info
44e7bb4389SLuca Ceresoli    -h                  Print this help and exit
45e7bb4389SLuca Ceresoli
46e7bb4389SLuca CeresoliThe output format is implied by the extension of OUT_FILE:
47e7bb4389SLuca Ceresoli
48e7bb4389SLuca Ceresoli * Use the .dot extension to generate a text graph representation in
49e7bb4389SLuca Ceresoli   graphviz dot syntax.
50e7bb4389SLuca Ceresoli * Any other extension is assumed to be a format supported by graphviz for
51e7bb4389SLuca Ceresoli   rendering, e.g. 'png', 'svg', and will produce both the .dot file and a
52e7bb4389SLuca Ceresoli   picture from it. This requires the 'dot' program from the graphviz
53e7bb4389SLuca Ceresoli   package.
54e7bb4389SLuca Ceresoli"
55e7bb4389SLuca Ceresoli
56e7bb4389SLuca Ceresoli    exit ${1}
57e7bb4389SLuca Ceresoli}
58e7bb4389SLuca Ceresoli
59e7bb4389SLuca Ceresoli# Connect to a remote target via SSH, collect all DAPM files from debufs
60e7bb4389SLuca Ceresoli# into a tarball and get the tarball via SCP into $3/dapm.tar
61e7bb4389SLuca Ceresoli#
62e7bb4389SLuca Ceresoli# $1 = target as used by ssh and scp, e.g. "root@192.168.1.1"
63e7bb4389SLuca Ceresoli# $2 = sound card name
64e7bb4389SLuca Ceresoli# $3 = temp dir path (present on the host, created on the target)
65e7bb4389SLuca Ceresoli# $4 = local directory to extract the tarball into
66e7bb4389SLuca Ceresoli#
67e7bb4389SLuca Ceresoli# Requires an ssh+scp server, find and tar+gz on the target
68e7bb4389SLuca Ceresoli#
69e7bb4389SLuca Ceresoli# Note: the tarball is needed because plain 'scp -r' from debugfs would
70e7bb4389SLuca Ceresoli# copy only empty files
71e7bb4389SLuca Ceresoligrab_remote_files()
72e7bb4389SLuca Ceresoli{
73e7bb4389SLuca Ceresoli    echo "Collecting DAPM state from ${1}"
74e7bb4389SLuca Ceresoli    dbg_echo "Collected DAPM state in ${3}"
75e7bb4389SLuca Ceresoli
76e7bb4389SLuca Ceresoli    ssh "${1}" "
77e7bb4389SLuca Ceresoliset -eu &&
78e7bb4389SLuca Ceresolicd \"/sys/kernel/debug/asoc/${2}\" &&
79e7bb4389SLuca Ceresolifind * -type d -exec mkdir -p ${3}/dapm-tree/{} \; &&
80e7bb4389SLuca Ceresolifind * -type f -exec cp \"{}\" \"${3}/dapm-tree/{}\" \; &&
81e7bb4389SLuca Ceresolicd ${3}/dapm-tree &&
82e7bb4389SLuca Ceresolitar cf ${3}/dapm.tar ."
83e7bb4389SLuca Ceresoli    scp -q "${1}:${3}/dapm.tar" "${3}"
84e7bb4389SLuca Ceresoli
85e7bb4389SLuca Ceresoli    mkdir -p "${4}"
86e7bb4389SLuca Ceresoli    tar xf "${tmp_dir}/dapm.tar" -C "${4}"
87e7bb4389SLuca Ceresoli}
88e7bb4389SLuca Ceresoli
89e7bb4389SLuca Ceresoli# Parse a widget file and generate graph description in graphviz dot format
90e7bb4389SLuca Ceresoli#
91e7bb4389SLuca Ceresoli# Skips any file named "bias_level".
92e7bb4389SLuca Ceresoli#
93e7bb4389SLuca Ceresoli# $1 = temporary work dir
94e7bb4389SLuca Ceresoli# $2 = component name
95e7bb4389SLuca Ceresoli# $3 = widget filename
96e7bb4389SLuca Ceresoliprocess_dapm_widget()
97e7bb4389SLuca Ceresoli{
98e7bb4389SLuca Ceresoli    local tmp_dir="${1}"
99e7bb4389SLuca Ceresoli    local c_name="${2}"
100e7bb4389SLuca Ceresoli    local w_file="${3}"
101e7bb4389SLuca Ceresoli    local dot_file="${tmp_dir}/main.dot"
102e7bb4389SLuca Ceresoli    local links_file="${tmp_dir}/links.dot"
103e7bb4389SLuca Ceresoli
104e7bb4389SLuca Ceresoli    local w_name="$(basename "${w_file}")"
105e7bb4389SLuca Ceresoli    local w_tag="${c_name}_${w_name}"
106e7bb4389SLuca Ceresoli
107e7bb4389SLuca Ceresoli    if [ "${w_name}" = "bias_level" ]; then
108e7bb4389SLuca Ceresoli	return 0
109e7bb4389SLuca Ceresoli    fi
110e7bb4389SLuca Ceresoli
111e7bb4389SLuca Ceresoli    dbg_echo "   + Widget: ${w_name}"
112e7bb4389SLuca Ceresoli
113e7bb4389SLuca Ceresoli    cat "${w_file}" | (
114e7bb4389SLuca Ceresoli 	read line
115e7bb4389SLuca Ceresoli
116e7bb4389SLuca Ceresoli 	if echo "${line}" | grep -q ': On '
117e7bb4389SLuca Ceresoli	then local node_style="${STYLE_NODE_ON}"
118e7bb4389SLuca Ceresoli	else local node_style="${STYLE_NODE_OFF}"
119e7bb4389SLuca Ceresoli 	fi
120e7bb4389SLuca Ceresoli
121e7bb4389SLuca Ceresoli	local w_type=""
122e7bb4389SLuca Ceresoli	while read line; do
123e7bb4389SLuca Ceresoli	    # Collect widget type if present
124e7bb4389SLuca Ceresoli	    if echo "${line}" | grep -q '^widget-type '; then
125e7bb4389SLuca Ceresoli		local w_type_raw="$(echo "$line" | cut -d ' ' -f 2)"
126e7bb4389SLuca Ceresoli		dbg_echo "     - Widget type: ${w_type_raw}"
127e7bb4389SLuca Ceresoli
128e7bb4389SLuca Ceresoli		# Note: escaping '\n' is tricky to get working with both
129e7bb4389SLuca Ceresoli		# bash and busybox ash, so use a '%' here and replace it
130e7bb4389SLuca Ceresoli		# later
131e7bb4389SLuca Ceresoli		local w_type="%n[${w_type_raw}]"
132e7bb4389SLuca Ceresoli	    fi
133e7bb4389SLuca Ceresoli
134e7bb4389SLuca Ceresoli	    # Collect any links. We could use "in" links or "out" links,
135e7bb4389SLuca Ceresoli	    # let's use "in" links
136e7bb4389SLuca Ceresoli	    if echo "${line}" | grep -q '^in '; then
137a14b278aSLuca Ceresoli		local w_route=$(echo "$line" | awk -F\" '{print $2}')
138e7bb4389SLuca Ceresoli		local w_src=$(echo "$line" |
139e7bb4389SLuca Ceresoli				  awk -F\" '{print $6 "_" $4}' |
140e7bb4389SLuca Ceresoli				  sed  's/^(null)_/ROOT_/')
141e7bb4389SLuca Ceresoli		dbg_echo "     - Input route from: ${w_src}"
142a14b278aSLuca Ceresoli		dbg_echo "     - Route: ${w_route}"
143a14b278aSLuca Ceresoli		local w_edge_attrs=""
144a14b278aSLuca Ceresoli		if [ "${w_route}" != "static" ]; then
145a14b278aSLuca Ceresoli		    w_edge_attrs=" [label=\"${w_route}\"]"
146a14b278aSLuca Ceresoli		fi
147a14b278aSLuca Ceresoli		echo "  \"${w_src}\" -> \"$w_tag\"${w_edge_attrs}" >> "${links_file}"
148e7bb4389SLuca Ceresoli	    fi
149e7bb4389SLuca Ceresoli	done
150e7bb4389SLuca Ceresoli
151e7bb4389SLuca Ceresoli	echo "    \"${w_tag}\" [label=\"${w_name}${w_type}\",${node_style}]" |
152e7bb4389SLuca Ceresoli	    tr '%' '\\' >> "${dot_file}"
153e7bb4389SLuca Ceresoli   )
154e7bb4389SLuca Ceresoli}
155e7bb4389SLuca Ceresoli
156e7bb4389SLuca Ceresoli# Parse the DAPM tree for a sound card component and generate graph
157e7bb4389SLuca Ceresoli# description in graphviz dot format
158e7bb4389SLuca Ceresoli#
159e7bb4389SLuca Ceresoli# $1 = temporary work dir
160e7bb4389SLuca Ceresoli# $2 = component directory
1615a98c2e5SLuca Ceresoli# $3 = "ROOT" for the root card directory, empty otherwise
162e7bb4389SLuca Ceresoliprocess_dapm_component()
163e7bb4389SLuca Ceresoli{
164e7bb4389SLuca Ceresoli    local tmp_dir="${1}"
165e7bb4389SLuca Ceresoli    local c_dir="${2}"
166e7bb4389SLuca Ceresoli    local c_name="${3}"
1675a98c2e5SLuca Ceresoli    local is_component=0
168e7bb4389SLuca Ceresoli    local dot_file="${tmp_dir}/main.dot"
169e7bb4389SLuca Ceresoli    local links_file="${tmp_dir}/links.dot"
17064a1e3ddSLuca Ceresoli    local c_attribs=""
171e7bb4389SLuca Ceresoli
172e7bb4389SLuca Ceresoli    if [ -z "${c_name}" ]; then
1735a98c2e5SLuca Ceresoli	is_component=1
1745a98c2e5SLuca Ceresoli
175e7bb4389SLuca Ceresoli	# Extract directory name into component name:
176e7bb4389SLuca Ceresoli	#   "./cs42l51.0-004a/dapm" -> "cs42l51.0-004a"
177e7bb4389SLuca Ceresoli	c_name="$(basename $(dirname "${c_dir}"))"
17864a1e3ddSLuca Ceresoli    fi
17964a1e3ddSLuca Ceresoli
18064a1e3ddSLuca Ceresoli    dbg_echo " * Component: ${c_name}"
18164a1e3ddSLuca Ceresoli
18264a1e3ddSLuca Ceresoli    if [ ${is_component} = 1 ]; then
18364a1e3ddSLuca Ceresoli	if [ -f "${c_dir}/bias_level" ]; then
18464a1e3ddSLuca Ceresoli	    c_onoff=$(sed -n -e 1p "${c_dir}/bias_level" | awk '{print $1}')
18564a1e3ddSLuca Ceresoli	    dbg_echo "   - bias_level: ${c_onoff}"
18664a1e3ddSLuca Ceresoli	    if [ "$c_onoff" = "On" ]; then
18764a1e3ddSLuca Ceresoli		c_attribs="${STYLE_COMPONENT_ON}"
18864a1e3ddSLuca Ceresoli	    elif [ "$c_onoff" = "Off" ]; then
18964a1e3ddSLuca Ceresoli		c_attribs="${STYLE_COMPONENT_OFF}"
19064a1e3ddSLuca Ceresoli	    fi
19164a1e3ddSLuca Ceresoli	fi
192e7bb4389SLuca Ceresoli
193e7bb4389SLuca Ceresoli	echo ""                           >> "${dot_file}"
194e7bb4389SLuca Ceresoli	echo "  subgraph \"${c_name}\" {" >> "${dot_file}"
195e7bb4389SLuca Ceresoli	echo "    cluster = true"         >> "${dot_file}"
196e7bb4389SLuca Ceresoli	echo "    label = \"${c_name}\""  >> "${dot_file}"
19764a1e3ddSLuca Ceresoli	echo "    ${c_attribs}"           >> "${dot_file}"
1985a98c2e5SLuca Ceresoli    fi
1995a98c2e5SLuca Ceresoli
200e7bb4389SLuca Ceresoli    # Create empty file to ensure it will exist in all cases
201e7bb4389SLuca Ceresoli    >"${links_file}"
202e7bb4389SLuca Ceresoli
203e7bb4389SLuca Ceresoli    # Iterate over widgets in the component dir
204e7bb4389SLuca Ceresoli    for w_file in ${c_dir}/*; do
205e7bb4389SLuca Ceresoli	process_dapm_widget "${tmp_dir}" "${c_name}" "${w_file}"
206e7bb4389SLuca Ceresoli    done
207e7bb4389SLuca Ceresoli
2085a98c2e5SLuca Ceresoli    if [ ${is_component} = 1 ]; then
209e7bb4389SLuca Ceresoli	echo "  }" >> "${dot_file}"
2105a98c2e5SLuca Ceresoli    fi
211e7bb4389SLuca Ceresoli
212e7bb4389SLuca Ceresoli    cat "${links_file}" >> "${dot_file}"
213e7bb4389SLuca Ceresoli}
214e7bb4389SLuca Ceresoli
215e7bb4389SLuca Ceresoli# Parse the DAPM tree for a sound card and generate graph description in
216e7bb4389SLuca Ceresoli# graphviz dot format
217e7bb4389SLuca Ceresoli#
218e7bb4389SLuca Ceresoli# $1 = temporary work dir
219e7bb4389SLuca Ceresoli# $2 = directory tree with DAPM state (either in debugfs or a mirror)
220e7bb4389SLuca Ceresoliprocess_dapm_tree()
221e7bb4389SLuca Ceresoli{
222e7bb4389SLuca Ceresoli    local tmp_dir="${1}"
223e7bb4389SLuca Ceresoli    local dapm_dir="${2}"
224e7bb4389SLuca Ceresoli    local dot_file="${tmp_dir}/main.dot"
225e7bb4389SLuca Ceresoli
226e7bb4389SLuca Ceresoli    echo "digraph G {" > "${dot_file}"
227e7bb4389SLuca Ceresoli    echo "  fontname=\"sans-serif\"" >> "${dot_file}"
228e7bb4389SLuca Ceresoli    echo "  node [fontname=\"sans-serif\"]" >> "${dot_file}"
229a14b278aSLuca Ceresoli    echo "  edge [fontname=\"sans-serif\"]" >> "${dot_file}"
230e7bb4389SLuca Ceresoli
231e7bb4389SLuca Ceresoli    # Process root directory (no component)
232e7bb4389SLuca Ceresoli    process_dapm_component "${tmp_dir}" "${dapm_dir}/dapm" "ROOT"
233e7bb4389SLuca Ceresoli
234e7bb4389SLuca Ceresoli    # Iterate over components
235e7bb4389SLuca Ceresoli    for c_dir in "${dapm_dir}"/*/dapm
236e7bb4389SLuca Ceresoli    do
237e7bb4389SLuca Ceresoli	process_dapm_component "${tmp_dir}" "${c_dir}" ""
238e7bb4389SLuca Ceresoli    done
239e7bb4389SLuca Ceresoli
240e7bb4389SLuca Ceresoli    echo "}" >> "${dot_file}"
241e7bb4389SLuca Ceresoli}
242e7bb4389SLuca Ceresoli
243e7bb4389SLuca Ceresolimain()
244e7bb4389SLuca Ceresoli{
245e7bb4389SLuca Ceresoli    # Parse command line
246e7bb4389SLuca Ceresoli    local out_file="dapm.dot"
247e7bb4389SLuca Ceresoli    local card_name=""
248e7bb4389SLuca Ceresoli    local remote_target=""
249e7bb4389SLuca Ceresoli    local dapm_tree=""
250e7bb4389SLuca Ceresoli    local dbg_on=""
251e7bb4389SLuca Ceresoli    while getopts "c:r:d:o:Dh" arg; do
252e7bb4389SLuca Ceresoli	case $arg in
253e7bb4389SLuca Ceresoli	    c)  card_name="${OPTARG}"      ;;
254e7bb4389SLuca Ceresoli	    r)  remote_target="${OPTARG}"  ;;
255e7bb4389SLuca Ceresoli	    d)  dapm_tree="${OPTARG}"      ;;
256e7bb4389SLuca Ceresoli	    o)  out_file="${OPTARG}"       ;;
257e7bb4389SLuca Ceresoli	    D)  dbg_on="1"                 ;;
258e7bb4389SLuca Ceresoli	    h)  usage 0                    ;;
259e7bb4389SLuca Ceresoli	    *)  usage 1                    ;;
260e7bb4389SLuca Ceresoli	esac
261e7bb4389SLuca Ceresoli    done
262e7bb4389SLuca Ceresoli    shift $(($OPTIND - 1))
263e7bb4389SLuca Ceresoli
264e7bb4389SLuca Ceresoli    if [ -n "${dapm_tree}" ]; then
265e7bb4389SLuca Ceresoli	if [ -n "${card_name}${remote_target}" ]; then
266e7bb4389SLuca Ceresoli	    usage 1 "Cannot use -c and -r with -d"
267e7bb4389SLuca Ceresoli	fi
268e7bb4389SLuca Ceresoli	echo "Using local tree: ${dapm_tree}"
269e7bb4389SLuca Ceresoli    elif [ -n "${remote_target}" ]; then
270e7bb4389SLuca Ceresoli	if [ -z "${card_name}" ]; then
271e7bb4389SLuca Ceresoli	    usage 1 "-r requires -c"
272e7bb4389SLuca Ceresoli	fi
273e7bb4389SLuca Ceresoli	echo "Using card ${card_name} from remote target ${remote_target}"
274e7bb4389SLuca Ceresoli    elif [ -n "${card_name}" ]; then
275e7bb4389SLuca Ceresoli	echo "Using local card: ${card_name}"
276e7bb4389SLuca Ceresoli    else
277e7bb4389SLuca Ceresoli	usage 1 "Please choose mode using -c, -r or -d"
278e7bb4389SLuca Ceresoli    fi
279e7bb4389SLuca Ceresoli
280e7bb4389SLuca Ceresoli    # Define logging function
281e7bb4389SLuca Ceresoli    if [ "${dbg_on}" ]; then
282e7bb4389SLuca Ceresoli	dbg_echo() {
283e7bb4389SLuca Ceresoli	    echo "$*" >&2
284e7bb4389SLuca Ceresoli	}
285e7bb4389SLuca Ceresoli    else
286e7bb4389SLuca Ceresoli	dbg_echo() {
287e7bb4389SLuca Ceresoli	    :
288e7bb4389SLuca Ceresoli	}
289e7bb4389SLuca Ceresoli    fi
290e7bb4389SLuca Ceresoli
291e7bb4389SLuca Ceresoli    # Filename must have a dot in order the infer the format from the
292e7bb4389SLuca Ceresoli    # extension
293e7bb4389SLuca Ceresoli    if ! echo "${out_file}" | grep -qE '\.'; then
294e7bb4389SLuca Ceresoli	echo "Missing extension in output filename ${out_file}" >&2
295e7bb4389SLuca Ceresoli	usage
296e7bb4389SLuca Ceresoli	exit 1
297e7bb4389SLuca Ceresoli    fi
298e7bb4389SLuca Ceresoli
299e7bb4389SLuca Ceresoli    local out_fmt="${out_file##*.}"
300e7bb4389SLuca Ceresoli    local dot_file="${out_file%.*}.dot"
301e7bb4389SLuca Ceresoli
302e7bb4389SLuca Ceresoli    dbg_echo "dot file:      $dot_file"
303e7bb4389SLuca Ceresoli    dbg_echo "Output file:   $out_file"
304e7bb4389SLuca Ceresoli    dbg_echo "Output format: $out_fmt"
305e7bb4389SLuca Ceresoli
306e7bb4389SLuca Ceresoli    tmp_dir="$(mktemp -d /tmp/$(basename $0).XXXXXX)"
307e7bb4389SLuca Ceresoli    trap "{ rm -fr ${tmp_dir}; }" INT TERM EXIT
308e7bb4389SLuca Ceresoli
309e7bb4389SLuca Ceresoli    if [ -z "${dapm_tree}" ]
310e7bb4389SLuca Ceresoli    then
311e7bb4389SLuca Ceresoli	dapm_tree="/sys/kernel/debug/asoc/${card_name}"
312e7bb4389SLuca Ceresoli    fi
313e7bb4389SLuca Ceresoli    if [ -n "${remote_target}" ]; then
314e7bb4389SLuca Ceresoli	dapm_tree="${tmp_dir}/dapm-tree"
315e7bb4389SLuca Ceresoli	grab_remote_files "${remote_target}" "${card_name}" "${tmp_dir}" "${dapm_tree}"
316e7bb4389SLuca Ceresoli    fi
317e7bb4389SLuca Ceresoli    # In all cases now ${dapm_tree} contains the DAPM state
318e7bb4389SLuca Ceresoli
319e7bb4389SLuca Ceresoli    process_dapm_tree "${tmp_dir}" "${dapm_tree}"
320e7bb4389SLuca Ceresoli    cp "${tmp_dir}/main.dot" "${dot_file}"
321e7bb4389SLuca Ceresoli
322e7bb4389SLuca Ceresoli    if [ "${out_file}" != "${dot_file}" ]; then
323e7bb4389SLuca Ceresoli	dot -T"${out_fmt}" "${dot_file}" -o "${out_file}"
324e7bb4389SLuca Ceresoli    fi
325e7bb4389SLuca Ceresoli
326e7bb4389SLuca Ceresoli    echo "Generated file ${out_file}"
327e7bb4389SLuca Ceresoli}
328e7bb4389SLuca Ceresoli
329e7bb4389SLuca Ceresolimain "${@}"
330