1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
8 #include "format-table.h"
11 #include "parse-argument.h"
12 #include "path-util.h"
13 #include "pretty-print.h"
14 #include "terminal-util.h"
19 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
20 static PagerFlags arg_pager_flags
= 0;
21 static VarlinkMethodFlags arg_method_flags
= 0;
23 static int help(void) {
24 _cleanup_free_
char *link
= NULL
;
27 r
= terminal_urlify_man("varlinkctl", "1", &link
);
31 pager_open(arg_pager_flags
);
33 printf("%1$s [OPTIONS...] COMMAND ...\n\n"
34 "%5$sIntrospect Varlink Services.%6$s\n"
35 "\n%3$sCommands:%4$s\n"
36 " info ADDRESS Show service information\n"
37 " list-interfaces ADDRESS\n"
38 " List interfaces implemented by service\n"
39 " introspect ADDRESS INTERFACE\n"
40 " Show interface definition\n"
41 " call ADDRESS METHOD [PARAMS]\n"
43 " validate-idl [FILE] Validate interface description\n"
44 " help Show this help\n"
45 "\n%3$sOptions:%4$s\n"
46 " -h --help Show this help\n"
47 " --version Show package version\n"
48 " --no-pager Do not pipe output into a pager\n"
49 " --more Request multiple responses\n"
50 " --oneway Do not request response\n"
51 " --json=MODE Output as JSON\n"
52 " -j Same as --json=pretty on tty, --json=short otherwise\n"
53 "\nSee the %2$s for details.\n",
54 program_invocation_short_name
,
64 static int verb_help(int argc
, char **argv
, void *userdata
) {
68 static int parse_argv(int argc
, char *argv
[]) {
78 static const struct option options
[] = {
79 { "help", no_argument
, NULL
, 'h' },
80 { "version", no_argument
, NULL
, ARG_VERSION
},
81 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
82 { "more", no_argument
, NULL
, ARG_MORE
},
83 { "oneway", no_argument
, NULL
, ARG_ONEWAY
},
84 { "json", required_argument
, NULL
, ARG_JSON
},
93 while ((c
= getopt_long(argc
, argv
, "hj", options
, NULL
)) >= 0)
104 arg_pager_flags
|= PAGER_DISABLE
;
108 arg_method_flags
= (arg_method_flags
& ~VARLINK_METHOD_ONEWAY
) | VARLINK_METHOD_MORE
;
112 arg_method_flags
= (arg_method_flags
& ~VARLINK_METHOD_MORE
) | VARLINK_METHOD_ONEWAY
;
116 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
123 arg_json_format_flags
= JSON_FORMAT_PRETTY_AUTO
|JSON_FORMAT_COLOR_AUTO
;
130 assert_not_reached();
133 /* If more than one reply is expected, imply JSON-SEQ output */
134 if (FLAGS_SET(arg_method_flags
, VARLINK_METHOD_MORE
))
135 arg_json_format_flags
|= JSON_FORMAT_SEQ
;
140 static int varlink_connect_auto(Varlink
**ret
, const char *where
) {
146 if (STARTSWITH_SET(where
, "/", "./")) { /* If the string starts with a slash or dot slash we use it as a file system path */
147 _cleanup_close_
int fd
= -EBADF
;
150 fd
= open(where
, O_PATH
|O_CLOEXEC
);
152 return log_error_errno(errno
, "Failed to open '%s': %m", where
);
154 if (fstat(fd
, &st
) < 0)
155 return log_error_errno(errno
, "Failed to stat '%s': %m", where
);
157 /* Is this a socket in the fs? Then connect() to it. */
158 if (S_ISSOCK(st
.st_mode
)) {
159 r
= varlink_connect_address(ret
, FORMAT_PROC_FD_PATH(fd
));
161 return log_error_errno(r
, "Failed to connect to '%s': %m", where
);
166 /* Is this an executable binary? Then fork it off. */
167 if (S_ISREG(st
.st_mode
) && (st
.st_mode
& 0111)) {
168 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 */
170 return log_error_errno(r
, "Failed to spawn '%s' process: %m", where
);
175 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Unrecognized path '%s' is neither an AF_UNIX socket, nor an executable binary.", where
);
178 /* Otherwise assume this is an URL */
179 r
= varlink_connect_url(ret
, where
);
181 return log_error_errno(r
, "Failed to connect to URL '%s': %m", where
);
186 typedef struct GetInfoData
{
194 static void get_info_data_done(GetInfoData
*d
) {
197 d
->interfaces
= strv_free(d
->interfaces
);
200 static int verb_info(int argc
, char *argv
[], void *userdata
) {
201 _cleanup_(varlink_unrefp
) Varlink
*vl
= NULL
;
208 r
= varlink_connect_auto(&vl
, url
);
212 JsonVariant
*reply
= NULL
;
213 const char *error
= NULL
;
214 r
= varlink_call(vl
, "org.varlink.service.GetInfo", NULL
, &reply
, &error
, NULL
);
216 return log_error_errno(r
, "Failed to issue GetInfo() call: %m");
218 return log_error_errno(SYNTHETIC_ERRNO(EBADE
), "Method call GetInfo() failed: %s", error
);
220 pager_open(arg_pager_flags
);
222 if (FLAGS_SET(arg_json_format_flags
, JSON_FORMAT_OFF
)) {
223 static const struct JsonDispatch dispatch_table
[] = {
224 { "vendor", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(GetInfoData
, vendor
), JSON_MANDATORY
},
225 { "product", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(GetInfoData
, product
), JSON_MANDATORY
},
226 { "version", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(GetInfoData
, version
), JSON_MANDATORY
},
227 { "url", JSON_VARIANT_STRING
, json_dispatch_const_string
, offsetof(GetInfoData
, url
), JSON_MANDATORY
},
228 { "interfaces", JSON_VARIANT_ARRAY
, json_dispatch_strv
, offsetof(GetInfoData
, interfaces
), JSON_MANDATORY
},
231 _cleanup_(get_info_data_done
) GetInfoData data
= {};
233 r
= json_dispatch(reply
, dispatch_table
, NULL
, JSON_LOG
, &data
);
237 strv_sort(data
.interfaces
);
239 if (streq_ptr(argv
[0], "list-interfaces")) {
240 STRV_FOREACH(i
, data
.interfaces
)
243 _cleanup_(table_unrefp
) Table
*t
= NULL
;
245 t
= table_new_vertical();
251 TABLE_FIELD
, "Vendor",
252 TABLE_STRING
, data
.vendor
,
253 TABLE_FIELD
, "Product",
254 TABLE_STRING
, data
.product
,
255 TABLE_FIELD
, "Version",
256 TABLE_STRING
, data
.version
,
258 TABLE_STRING
, data
.url
,
259 TABLE_SET_URL
, data
.url
,
260 TABLE_FIELD
, "Interfaces",
261 TABLE_STRV
, data
.interfaces
);
263 return table_log_add_error(r
);
265 r
= table_print(t
, NULL
);
267 return table_log_print_error(r
);
272 v
= streq_ptr(argv
[0], "list-interfaces") ?
273 json_variant_by_key(reply
, "interfaces") : reply
;
275 json_variant_dump(v
, arg_json_format_flags
, stdout
, NULL
);
281 typedef struct GetInterfaceDescriptionData
{
282 const char *description
;
283 } GetInterfaceDescriptionData
;
285 static int verb_introspect(int argc
, char *argv
[], void *userdata
) {
286 _cleanup_(varlink_unrefp
) Varlink
*vl
= NULL
;
287 const char *url
, *interface
;
294 r
= varlink_connect_auto(&vl
, url
);
298 JsonVariant
*reply
= NULL
;
299 const char *error
= NULL
;
300 r
= varlink_callb(vl
, "org.varlink.service.GetInterfaceDescription", &reply
, &error
, NULL
, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface
)));
302 return log_error_errno(r
, "Failed to issue GetInterfaceDescription() call: %m");
304 return log_error_errno(SYNTHETIC_ERRNO(EBADE
), "Method call GetInterfaceDescription() failed: %s", error
);
306 pager_open(arg_pager_flags
);
308 if (FLAGS_SET(arg_json_format_flags
, JSON_FORMAT_OFF
)) {
309 static const struct JsonDispatch dispatch_table
[] = {
310 { "description", JSON_VARIANT_STRING
, json_dispatch_const_string
, 0, JSON_MANDATORY
},
313 _cleanup_(varlink_interface_freep
) VarlinkInterface
*vi
= NULL
;
314 const char *description
= NULL
;
315 unsigned line
= 0, column
= 0;
317 r
= json_dispatch(reply
, dispatch_table
, NULL
, JSON_LOG
, &description
);
321 /* Try to parse the returned description, so that we can add syntax highlighting */
322 r
= varlink_idl_parse(ASSERT_PTR(description
), &line
, &column
, &vi
);
324 log_warning_errno(r
, "Failed to parse returned interface description at %u:%u, showing raw interface description: %m", line
, column
);
326 fputs(description
, stdout
);
327 if (!endswith(description
, "\n"))
330 r
= varlink_idl_dump(stdout
, /* use_colors= */ -1, vi
);
332 return log_error_errno(r
, "Failed to format parsed interface description: %m");
335 json_variant_dump(reply
, arg_json_format_flags
, stdout
, NULL
);
340 static int reply_callback(
342 JsonVariant
*parameters
,
344 VarlinkReplyFlags flags
,
352 /* Propagate the error we received via sd_notify() */
353 (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error
);
355 r
= log_error_errno(SYNTHETIC_ERRNO(EBADE
), "Method call failed: %s", error
);
359 json_variant_dump(parameters
, arg_json_format_flags
, stdout
, NULL
);
363 static int verb_call(int argc
, char *argv
[], void *userdata
) {
364 _cleanup_(json_variant_unrefp
) JsonVariant
*jp
= NULL
;
365 _cleanup_(varlink_unrefp
) Varlink
*vl
= NULL
;
366 const char *url
, *method
, *parameter
;
367 unsigned line
= 0, column
= 0;
374 parameter
= argc
> 3 && !streq(argv
[3], "-") ? argv
[3] : NULL
;
376 arg_json_format_flags
&= ~JSON_FORMAT_OFF
;
379 r
= json_parse_with_source(parameter
, "<argv[4]>", 0, &jp
, &line
, &column
);
381 return log_error_errno(r
, "Failed to parse parameters at <argv[4]>:%u:%u: %m", line
, column
);
383 r
= json_parse_file_at(stdin
, AT_FDCWD
, "<stdin>", 0, &jp
, &line
, &column
);
385 return log_error_errno(r
, "Failed to parse parameters at <stdin>:%u:%u: %m", line
, column
);
388 r
= varlink_connect_auto(&vl
, url
);
392 if (arg_method_flags
& VARLINK_METHOD_ONEWAY
) {
393 r
= varlink_send(vl
, method
, jp
);
395 return log_error_errno(r
, "Failed to issue %s() call: %m", method
);
397 r
= varlink_flush(vl
);
399 return log_error_errno(r
, "Failed to flush Varlink connection: %m");
401 } else if (arg_method_flags
& VARLINK_METHOD_MORE
) {
403 varlink_set_userdata(vl
, (void*) method
);
405 r
= varlink_bind_reply(vl
, reply_callback
);
407 return log_error_errno(r
, "Failed to bind reply callback: %m");
409 r
= varlink_observe(vl
, method
, jp
);
411 return log_error_errno(r
, "Failed to issue %s() call: %m", method
);
414 r
= varlink_is_idle(vl
);
416 return log_error_errno(r
, "Failed to check if varlink connection is idle: %m");
420 r
= varlink_process(vl
);
422 return log_error_errno(r
, "Failed to process varlink connection: %m");
426 r
= varlink_wait(vl
, USEC_INFINITY
);
428 return log_error_errno(r
, "Failed to wait for varlink connection events: %m");
431 JsonVariant
*reply
= NULL
;
432 const char *error
= NULL
;
434 r
= varlink_call(vl
, method
, jp
, &reply
, &error
, NULL
);
436 return log_error_errno(r
, "Failed to issue %s() call: %m", method
);
438 /* If the server returned an error to us, then fail, but first output the associated parameters */
440 /* Propagate the error we received via sd_notify() */
441 (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error
);
443 r
= log_error_errno(SYNTHETIC_ERRNO(EBADE
), "Method call %s() failed: %s", method
, error
);
447 pager_open(arg_pager_flags
);
449 json_variant_dump(reply
, arg_json_format_flags
, stdout
, NULL
);
456 static int verb_validate_idl(int argc
, char *argv
[], void *userdata
) {
457 _cleanup_(varlink_interface_freep
) VarlinkInterface
*vi
= NULL
;
458 _cleanup_free_
char *text
= NULL
;
460 unsigned line
= 1, column
= 1;
463 fname
= argc
> 1 ? argv
[1] : NULL
;
466 r
= read_full_file(fname
, &text
, NULL
);
468 return log_error_errno(r
, "Failed to read interface description file '%s': %m", fname
);
470 r
= read_full_stream(stdin
, &text
, NULL
);
472 return log_error_errno(r
, "Failed to read interface description from stdin: %m");
477 r
= varlink_idl_parse(text
, &line
, &column
, &vi
);
479 return log_error_errno(r
, "%s:%u:%u: Bad syntax.", fname
, line
, column
);
480 if (r
== -ENETUNREACH
)
481 return log_error_errno(r
, "%s:%u:%u: Failed to parse interface description due an unresolved type.", fname
, line
, column
);
483 return log_error_errno(r
, "%s:%u:%u: Failed to parse interface description: %m", fname
, line
, column
);
485 r
= varlink_idl_consistent(vi
, LOG_ERR
);
487 return log_error_errno(r
, "Interface is inconsistent.");
489 return log_error_errno(r
, "Field or symbol not unique in interface.");
491 return log_error_errno(r
, "Failed to check interface for consistency: %m");
493 pager_open(arg_pager_flags
);
495 r
= varlink_idl_dump(stdout
, /* use_colors= */ -1, vi
);
497 return log_error_errno(r
, "Failed to format parsed interface description: %m");
502 static int varlinkctl_main(int argc
, char *argv
[]) {
503 static const Verb verbs
[] = {
504 { "info", 2, 2, 0, verb_info
},
505 { "list-interfaces", 2, 2, 0, verb_info
},
506 { "introspect", 3, 3, 0, verb_introspect
},
507 { "call", 3, 4, 0, verb_call
},
508 { "validate-idl", 1, 2, 0, verb_validate_idl
},
509 { "help", VERB_ANY
, VERB_ANY
, 0, verb_help
},
513 return dispatch_verb(argc
, argv
, verbs
, NULL
);
516 static int run(int argc
, char *argv
[]) {
521 r
= parse_argv(argc
, argv
);
525 return varlinkctl_main(argc
, argv
);
528 DEFINE_MAIN_FUNCTION(run
);