mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
varlinkctl: add new varlinkctl tool
This commit is contained in:
parent
02b0d24d1e
commit
d408a53f78
@ -582,6 +582,7 @@ o "/org/freedesktop/systemd1/job/42684"</programlisting>
|
||||
<citerefentry project='dbus'><refentrytitle>dbus-daemon</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<ulink url="https://www.freedesktop.org/wiki/Software/dbus">D-Bus</ulink>,
|
||||
<citerefentry><refentrytitle>sd-bus</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>varlinkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry project='die-net'><refentrytitle>wireshark</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
|
@ -1249,6 +1249,7 @@ manpages = [
|
||||
['systemd-user-runtime-dir', 'user-runtime-dir@.service'],
|
||||
''],
|
||||
['userdbctl', '1', [], 'ENABLE_USERDB'],
|
||||
['varlinkctl', '1', [], ''],
|
||||
['vconsole.conf', '5', [], 'ENABLE_VCONSOLE'],
|
||||
['veritytab', '5', [], 'HAVE_LIBCRYPTSETUP']
|
||||
]
|
||||
|
315
man/varlinkctl.xml
Normal file
315
man/varlinkctl.xml
Normal file
@ -0,0 +1,315 @@
|
||||
<?xml version='1.0'?>
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
|
||||
<refentry id="varlinkctl"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>varlinkctl</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>varlinkctl</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>varlinkctl</refname>
|
||||
<refpurpose>Introspect with and invoke Varlink services</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>varlinkctl</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="plain">info</arg>
|
||||
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
|
||||
<cmdsynopsis>
|
||||
<command>varlinkctl</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="plain">list-interfaces</arg>
|
||||
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
|
||||
<cmdsynopsis>
|
||||
<command>varlinkctl</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="plain">introspect</arg>
|
||||
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
|
||||
<arg choice="plain"><replaceable>INTERFACE</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
|
||||
<cmdsynopsis>
|
||||
<command>varlinkctl</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="plain">call</arg>
|
||||
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
|
||||
<arg choice="plain"><replaceable>METHOD</replaceable></arg>
|
||||
<arg choice="opt"><replaceable>PARAMETERS</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
|
||||
<cmdsynopsis>
|
||||
<command>varlinkctl</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="plain">validate-idl</arg>
|
||||
<arg choice="opt"><replaceable>FILE</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>varlinkctl</command> may be used to introspect and invoke <ulink
|
||||
url="https://varlink.org/">Varlink</ulink> services.</para>
|
||||
|
||||
<para>Services are referenced by one of the following:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>A Varlink service reference starting with the <literal>unix:</literal> string, followed
|
||||
by an absolute <constant>AF_UNIX</constant> path, or by <literal>@</literal> and an arbitrary string
|
||||
(the latter for referencing sockets in the abstract namespace).</para></listitem>
|
||||
|
||||
<listitem><para>A Varlink service reference starting with the <literal>exec:</literal> string, followed
|
||||
by an absolute path of a binary to execute.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>For convenience these two simpler (redundant) service address syntaxes are also supported:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>A file system path to an <constant>AF_UNIX</constant> socket, either absolute
|
||||
(i.e. begins with <literal>/</literal>) or relative (in which case it must begin with
|
||||
<literal>./</literal>).</para></listitem>
|
||||
|
||||
<listitem><para>A file system path to an executable, either absolute or relative (as above, must begin
|
||||
with <literal>/</literal>, resp. <literal>./</literal>).</para></listitem>
|
||||
</itemizedlist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Commands</title>
|
||||
|
||||
<para>The following commands are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><command>info</command> <replaceable>ADDRESS</replaceable></term>
|
||||
|
||||
<listitem><para>Show brief information about the specified service, including vendor name and list of
|
||||
implemented interfaces. Expects a service address in the formats described above.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>list-interfaces</command> <replaceable>ADDRESS</replaceable></term>
|
||||
|
||||
<listitem><para>Show list of interfaces implemented by the specified service. Expects a service
|
||||
address in the formats described above.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>introspect</command> <replaceable>ADDRESS</replaceable> <replaceable>INTERFACE</replaceable></term>
|
||||
|
||||
<listitem><para>Show interface definition of the specified interface provided by the specified
|
||||
service. Expects a service address in the formats described above and a Varlink interface
|
||||
name.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>call</command> <replaceable>ADDRESS</replaceable> <replaceable>METHOD</replaceable> [<replaceable>ARGUMENTS</replaceable>]</term>
|
||||
|
||||
<listitem><para>Call the specified method of the specified service. Expects a service address in the
|
||||
format described above, a fully qualified Varlink method name, and a JSON arguments object. If the
|
||||
arguments object is not specified, it is read from STDIN instead. To pass an empty list of
|
||||
parameters, specify the empty object <literal>{}</literal>.</para>
|
||||
|
||||
<para>The reply parameters are written as JSON object to STDOUT.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>validate-idl</command> [<replaceable>FILE</replaceable>]</term>
|
||||
|
||||
<listitem><para>Reads a Varlink interface definition file, parses and validates it, then outputs it
|
||||
with syntax highlighting. This checks for syntax and internal consistency of the interface. Expects a
|
||||
file name to read the interface definition from. If omitted reads the interface definition from
|
||||
STDIN.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>help</command></term>
|
||||
|
||||
<listitem><para>Show command syntax help.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>The following options are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--more</option></term>
|
||||
|
||||
<listitem><para>When used with <command>call</command>: expect multiple method replies. If this flag is
|
||||
set the method call is sent with the <constant>more</constant> flag set, which tells the service to
|
||||
generate multiple replies, if needed. The command remains running until the service sends a reply
|
||||
message that indicates it is the last in the series. This flag should be set only for method calls
|
||||
that support this mechanism.</para>
|
||||
|
||||
<para>If this mode is enabled output is automatically switched to JSON-SEQ mode, so that individual
|
||||
reply objects can be easily discerned.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--oneway</option></term>
|
||||
|
||||
<listitem><para>When used with <command>call</command>: do not expect a method reply. If this flag
|
||||
is set the method call is sent with the <constant>oneway</constant> flag set (the command exits
|
||||
immediately after), which tells the service not to generate a reply.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--json=</option><replaceable>MODE</replaceable></term>
|
||||
|
||||
<listitem>
|
||||
<para>Selects the JSON output formatting, one of <literal>pretty</literal> (for nicely indented,
|
||||
colorized output) or <literal>short</literal> (for terse output with minimal whitespace and no
|
||||
newlines), defaults to <literal>short</literal>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-j</option></term>
|
||||
|
||||
<listitem>
|
||||
<para>Equivalent to <option>--json=pretty</option> when invoked interactively from a terminal. Otherwise
|
||||
equivalent to <option>--json=short</option>, in particular when the output is piped to some other
|
||||
program.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Examples</title>
|
||||
|
||||
<example>
|
||||
<title>Investigating a Service</title>
|
||||
|
||||
<para>The following three commands inspect the <literal>io.systemd.Resolve</literal> service
|
||||
implemented by
|
||||
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
listing general service information and implemented interfaces, and then displaying the interface
|
||||
definition of its primary interface:</para>
|
||||
|
||||
<programlisting>$ varlinkctl info /run/systemd/resolve/io.systemd.Resolve
|
||||
Vendor: The systemd Project
|
||||
Product: systemd (systemd-resolved)
|
||||
Version: 254 (254-1522-g4790521^)
|
||||
URL: https://systemd.io/
|
||||
Interfaces: io.systemd
|
||||
io.systemd.Resolve
|
||||
org.varlink.service
|
||||
$ varlinkctl list-interfaces /run/systemd/resolve/io.systemd.Resolve
|
||||
io.systemd
|
||||
io.systemd.Resolve
|
||||
org.varlink.service
|
||||
$ varlinkctl introspect /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve
|
||||
interface io.systemd.Resolve
|
||||
type ResolvedAddress(
|
||||
ifindex: ?int,
|
||||
…</programlisting>
|
||||
|
||||
<para>(Interface definition has been truncated in the example above, in the interest of brevity.)</para>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Invoking a Method</title>
|
||||
|
||||
<para>The following command resolves a hostname via <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>'s <function>ResolveHostname</function> method call.</para>
|
||||
|
||||
<programlisting>$ varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name":"systemd.io","family":2}' -j
|
||||
{
|
||||
"addresses" : [
|
||||
{
|
||||
"ifindex" : 2,
|
||||
"family" : 2,
|
||||
"address" : [
|
||||
185,
|
||||
199,
|
||||
111,
|
||||
153
|
||||
]
|
||||
}
|
||||
],
|
||||
"name" : "systemd.io",
|
||||
"flags" : 1048577
|
||||
}</programlisting>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Investigating a Service Executable</title>
|
||||
|
||||
<para>The following comand inspects the <filename>/usr/lib/systemd/systemd-pcrextend</filename>
|
||||
executable and the IPC APIs it provides. It then invokes a method on it:</para>
|
||||
|
||||
<programlisting># varlinkctl info /usr/lib/systemd/systemd-pcrextend
|
||||
Vendor: The systemd Project
|
||||
Product: systemd (systemd-pcrextend)
|
||||
Version: 254 (254-1536-g97734fb)
|
||||
URL: https://systemd.io/
|
||||
Interfaces: io.systemd
|
||||
io.systemd.PCRExtend
|
||||
org.varlink.service
|
||||
# varlinkctl introspect /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend
|
||||
interface io.systemd.PCRExtend
|
||||
|
||||
method Extend(
|
||||
pcr: int,
|
||||
text: ?string,
|
||||
data: ?string
|
||||
) -> ()
|
||||
# varlinkctl call /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend.Extend '{"pcr":15,"text":"foobar"}'
|
||||
{}</programlisting>
|
||||
</example>
|
||||
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
|
||||
<para>
|
||||
<citerefentry><refentrytitle>busctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<ulink url="https://varlink.org/">Varlink</ulink>
|
||||
</para>
|
||||
</refsect1>
|
||||
</refentry>
|
@ -2193,6 +2193,7 @@ subdir('src/update-done')
|
||||
subdir('src/update-utmp')
|
||||
subdir('src/user-sessions')
|
||||
subdir('src/userdb')
|
||||
subdir('src/varlinkctl')
|
||||
subdir('src/vconsole')
|
||||
subdir('src/veritysetup')
|
||||
subdir('src/volatile-root')
|
||||
|
13
src/varlinkctl/meson.build
Normal file
13
src/varlinkctl/meson.build
Normal file
@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
varlinkctl_sources = files(
|
||||
'varlinkctl.c',
|
||||
)
|
||||
|
||||
executables += [
|
||||
executable_template + {
|
||||
'name' : 'varlinkctl',
|
||||
'public' : true,
|
||||
'sources' : varlinkctl_sources,
|
||||
},
|
||||
]
|
520
src/varlinkctl/varlinkctl.c
Normal file
520
src/varlinkctl/varlinkctl.c
Normal file
@ -0,0 +1,520 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include "build.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-table.h"
|
||||
#include "main-func.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "terminal-util.h"
|
||||
#include "varlink.h"
|
||||
#include "verbs.h"
|
||||
#include "version.h"
|
||||
|
||||
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
static VarlinkMethodFlags arg_method_flags = 0;
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("varlinkctl", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
pager_open(arg_pager_flags);
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n\n"
|
||||
"%5$sIntrospect Varlink Services.%6$s\n"
|
||||
"\n%3$sCommands:%4$s\n"
|
||||
" info ADDRESS Show service information\n"
|
||||
" list-interfaces ADDRESS\n"
|
||||
" List interfaces implemented by service\n"
|
||||
" introspect ADDRESS INTERFACE\n"
|
||||
" Show interface definition\n"
|
||||
" call ADDRESS METHOD [PARAMS]\n"
|
||||
" Invoke method\n"
|
||||
" validate-idl [FILE] Validate interface description\n"
|
||||
" help Show this help\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --more Request multiple responses\n"
|
||||
" --oneway Do not request response\n"
|
||||
" --json=MODE Output as JSON\n"
|
||||
" -j Same as --json=pretty on tty, --json=short otherwise\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_help(int argc, char **argv, void *userdata) {
|
||||
return help();
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_NO_PAGER,
|
||||
ARG_MORE,
|
||||
ARG_ONEWAY,
|
||||
ARG_JSON,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "more", no_argument, NULL, ARG_MORE },
|
||||
{ "oneway", no_argument, NULL, ARG_ONEWAY },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{},
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hj", options, NULL)) >= 0)
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case ARG_MORE:
|
||||
arg_method_flags = (arg_method_flags & ~VARLINK_METHOD_ONEWAY) | VARLINK_METHOD_MORE;
|
||||
break;
|
||||
|
||||
case ARG_ONEWAY:
|
||||
arg_method_flags = (arg_method_flags & ~VARLINK_METHOD_MORE) | VARLINK_METHOD_ONEWAY;
|
||||
break;
|
||||
|
||||
case ARG_JSON:
|
||||
r = parse_json_argument(optarg, &arg_json_format_flags);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case 'j':
|
||||
arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
/* If more than one reply is expected, imply JSON-SEQ output */
|
||||
if (FLAGS_SET(arg_method_flags, VARLINK_METHOD_MORE))
|
||||
arg_json_format_flags |= JSON_FORMAT_SEQ;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int varlink_connect_auto(Varlink **ret, const char *where) {
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
assert(where);
|
||||
|
||||
if (STARTSWITH_SET(where, "/", "./")) { /* If the string starts with a slash or dot slash we use it as a file system path */
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
struct stat st;
|
||||
|
||||
fd = open(where, O_PATH|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to open '%s': %m", where);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_error_errno(errno, "Failed to stat '%s': %m", where);
|
||||
|
||||
/* Is this a socket in the fs? Then connect() to it. */
|
||||
if (S_ISSOCK(st.st_mode)) {
|
||||
r = varlink_connect_address(ret, FORMAT_PROC_FD_PATH(fd));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to '%s': %m", where);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Is this an executable binary? Then fork it off. */
|
||||
if (S_ISREG(st.st_mode) && (st.st_mode & 0111)) {
|
||||
r = varlink_connect_exec(ret, where, STRV_MAKE(where)); /* Ideally we'd use FORMAT_PROC_FD_PATH(fd) here too, but that breaks the #! logic */
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to spawn '%s' process: %m", where);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unrecognized path '%s' is neither an AF_UNIX socket, nor an executable binary.", where);
|
||||
}
|
||||
|
||||
/* Otherwise assume this is an URL */
|
||||
r = varlink_connect_url(ret, where);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to URL '%s': %m", where);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct GetInfoData {
|
||||
const char *vendor;
|
||||
const char *product;
|
||||
const char *version;
|
||||
const char *url;
|
||||
char **interfaces;
|
||||
} GetInfoData;
|
||||
|
||||
static void get_info_data_done(GetInfoData *d) {
|
||||
assert(d);
|
||||
|
||||
d->interfaces = strv_free(d->interfaces);
|
||||
}
|
||||
|
||||
static int verb_info(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
const char *url;
|
||||
int r;
|
||||
|
||||
assert(argc == 2);
|
||||
url = argv[1];
|
||||
|
||||
r = varlink_connect_auto(&vl, url);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
JsonVariant *reply = NULL;
|
||||
const char *error = NULL;
|
||||
r = varlink_call(vl, "org.varlink.service.GetInfo", NULL, &reply, &error, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to issue GetInfo() call: %m");
|
||||
if (error)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInfo() failed: %s", error);
|
||||
|
||||
pager_open(arg_pager_flags);
|
||||
|
||||
if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
|
||||
static const struct JsonDispatch dispatch_table[] = {
|
||||
{ "vendor", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(GetInfoData, vendor), JSON_MANDATORY },
|
||||
{ "product", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(GetInfoData, product), JSON_MANDATORY },
|
||||
{ "version", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(GetInfoData, version), JSON_MANDATORY },
|
||||
{ "url", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(GetInfoData, url), JSON_MANDATORY },
|
||||
{ "interfaces", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(GetInfoData, interfaces), JSON_MANDATORY },
|
||||
{}
|
||||
};
|
||||
_cleanup_(get_info_data_done) GetInfoData data = {};
|
||||
|
||||
r = json_dispatch(reply, dispatch_table, NULL, JSON_LOG, &data);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
strv_sort(data.interfaces);
|
||||
|
||||
if (streq_ptr(argv[0], "list-interfaces")) {
|
||||
STRV_FOREACH(i, data.interfaces)
|
||||
puts(*i);
|
||||
} else {
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
|
||||
t = table_new_vertical();
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
r = table_add_many(
|
||||
t,
|
||||
TABLE_FIELD, "Vendor",
|
||||
TABLE_STRING, data.vendor,
|
||||
TABLE_FIELD, "Product",
|
||||
TABLE_STRING, data.product,
|
||||
TABLE_FIELD, "Version",
|
||||
TABLE_STRING, data.version,
|
||||
TABLE_FIELD, "URL",
|
||||
TABLE_STRING, data.url,
|
||||
TABLE_SET_URL, data.url,
|
||||
TABLE_FIELD, "Interfaces",
|
||||
TABLE_STRV, data.interfaces);
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
|
||||
r = table_print(t, NULL);
|
||||
if (r < 0)
|
||||
return table_log_print_error(r);
|
||||
}
|
||||
} else {
|
||||
JsonVariant *v;
|
||||
|
||||
v = streq_ptr(argv[0], "list-interfaces") ?
|
||||
json_variant_by_key(reply, "interfaces") : reply;
|
||||
|
||||
json_variant_dump(v, arg_json_format_flags, stdout, NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct GetInterfaceDescriptionData {
|
||||
const char *description;
|
||||
} GetInterfaceDescriptionData;
|
||||
|
||||
static int verb_introspect(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
const char *url, *interface;
|
||||
int r;
|
||||
|
||||
assert(argc == 3);
|
||||
url = argv[1];
|
||||
interface = argv[2];
|
||||
|
||||
r = varlink_connect_auto(&vl, url);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
JsonVariant *reply = NULL;
|
||||
const char *error = NULL;
|
||||
r = varlink_callb(vl, "org.varlink.service.GetInterfaceDescription", &reply, &error, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface)));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to issue GetInterfaceDescription() call: %m");
|
||||
if (error)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInterfaceDescription() failed: %s", error);
|
||||
|
||||
pager_open(arg_pager_flags);
|
||||
|
||||
if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
|
||||
static const struct JsonDispatch dispatch_table[] = {
|
||||
{ "description", JSON_VARIANT_STRING, json_dispatch_const_string, 0, JSON_MANDATORY },
|
||||
{}
|
||||
};
|
||||
_cleanup_(varlink_interface_freep) VarlinkInterface *vi = NULL;
|
||||
const char *description = NULL;
|
||||
unsigned line = 0, column = 0;
|
||||
|
||||
r = json_dispatch(reply, dispatch_table, NULL, JSON_LOG, &description);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Try to parse the returned description, so that we can add syntax highlighting */
|
||||
r = varlink_idl_parse(ASSERT_PTR(description), &line, &column, &vi);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to parse returned interface description at %u:%u, showing raw interface description: %m", line, column);
|
||||
|
||||
fputs(description, stdout);
|
||||
if (!endswith(description, "\n"))
|
||||
fputs("\n", stdout);
|
||||
} else {
|
||||
r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format parsed interface description: %m");
|
||||
}
|
||||
} else
|
||||
json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reply_callback(
|
||||
Varlink *link,
|
||||
JsonVariant *parameters,
|
||||
const char *error,
|
||||
VarlinkReplyFlags flags,
|
||||
void *userdata) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
if (error) {
|
||||
/* Propagate the error we received via sd_notify() */
|
||||
(void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);
|
||||
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error);
|
||||
} else
|
||||
r = 0;
|
||||
|
||||
json_variant_dump(parameters, arg_json_format_flags, stdout, NULL);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int verb_call(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *jp = NULL;
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
const char *url, *method, *parameter;
|
||||
unsigned line = 0, column = 0;
|
||||
int r;
|
||||
|
||||
assert(argc >= 3);
|
||||
assert(argc <= 4);
|
||||
url = argv[1];
|
||||
method = argv[2];
|
||||
parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL;
|
||||
|
||||
arg_json_format_flags &= ~JSON_FORMAT_OFF;
|
||||
|
||||
if (parameter) {
|
||||
r = json_parse_with_source(parameter, "<argv[4]>", 0, &jp, &line, &column);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse parameters at <argv[4]>:%u:%u: %m", line, column);
|
||||
} else {
|
||||
r = json_parse_file_at(stdin, AT_FDCWD, "<stdin>", 0, &jp, &line, &column);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse parameters at <stdin>:%u:%u: %m", line, column);
|
||||
}
|
||||
|
||||
r = varlink_connect_auto(&vl, url);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_method_flags & VARLINK_METHOD_ONEWAY) {
|
||||
r = varlink_send(vl, method, jp);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to issue %s() call: %m", method);
|
||||
|
||||
r = varlink_flush(vl);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to flush Varlink connection: %m");
|
||||
|
||||
} else if (arg_method_flags & VARLINK_METHOD_MORE) {
|
||||
|
||||
varlink_set_userdata(vl, (void*) method);
|
||||
|
||||
r = varlink_bind_reply(vl, reply_callback);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind reply callback: %m");
|
||||
|
||||
r = varlink_observe(vl, method, jp);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to issue %s() call: %m", method);
|
||||
|
||||
for (;;) {
|
||||
r = varlink_is_idle(vl);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if varlink connection is idle: %m");
|
||||
if (r > 0)
|
||||
break;
|
||||
|
||||
r = varlink_process(vl);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to process varlink connection: %m");
|
||||
if (r != 0)
|
||||
continue;
|
||||
|
||||
r = varlink_wait(vl, USEC_INFINITY);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to wait for varlink connection events: %m");
|
||||
}
|
||||
} else {
|
||||
JsonVariant *reply = NULL;
|
||||
const char *error = NULL;
|
||||
|
||||
r = varlink_call(vl, method, jp, &reply, &error, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to issue %s() call: %m", method);
|
||||
|
||||
/* If the server returned an error to us, then fail, but first output the associated parameters */
|
||||
if (error) {
|
||||
/* Propagate the error we received via sd_notify() */
|
||||
(void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);
|
||||
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error);
|
||||
} else
|
||||
r = 0;
|
||||
|
||||
pager_open(arg_pager_flags);
|
||||
|
||||
json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_validate_idl(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(varlink_interface_freep) VarlinkInterface *vi = NULL;
|
||||
_cleanup_free_ char *text = NULL;
|
||||
const char *fname;
|
||||
unsigned line = 1, column = 1;
|
||||
int r;
|
||||
|
||||
fname = argc > 1 ? argv[1] : NULL;
|
||||
|
||||
if (fname) {
|
||||
r = read_full_file(fname, &text, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read interface description file '%s': %m", fname);
|
||||
} else {
|
||||
r = read_full_stream(stdin, &text, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read interface description from stdin: %m");
|
||||
|
||||
fname = "<stdin>";
|
||||
}
|
||||
|
||||
r = varlink_idl_parse(text, &line, &column, &vi);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "%s:%u:%u: Failed to parse interface description: %m", fname, line, column);
|
||||
|
||||
r = varlink_idl_consistent(vi, LOG_ERR);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
pager_open(arg_pager_flags);
|
||||
|
||||
r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format parsed interface description: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int varlinkctl_main(int argc, char *argv[]) {
|
||||
static const Verb verbs[] = {
|
||||
{ "info", 2, 2, 0, verb_info },
|
||||
{ "list-interfaces", 2, 2, 0, verb_info },
|
||||
{ "introspect", 3, 3, 0, verb_introspect },
|
||||
{ "call", 3, 4, 0, verb_call },
|
||||
{ "validate-idl", 1, 2, 0, verb_validate_idl },
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, verb_help },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return varlinkctl_main(argc, argv);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
Loading…
Reference in New Issue
Block a user