]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/varlinkctl/varlinkctl.c
varlink: drop "ret_flags" parameter from varlink_call()
[thirdparty/systemd.git] / src / varlinkctl / varlinkctl.c
CommitLineData
d408a53f
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <getopt.h>
4
5#include "build.h"
6#include "fd-util.h"
7#include "fileio.h"
8#include "format-table.h"
9#include "main-func.h"
10#include "pager.h"
11#include "parse-argument.h"
12#include "path-util.h"
13#include "pretty-print.h"
14#include "terminal-util.h"
15#include "varlink.h"
16#include "verbs.h"
17#include "version.h"
18
19static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
20static PagerFlags arg_pager_flags = 0;
21static VarlinkMethodFlags arg_method_flags = 0;
22
23static int help(void) {
24 _cleanup_free_ char *link = NULL;
25 int r;
26
27 r = terminal_urlify_man("varlinkctl", "1", &link);
28 if (r < 0)
29 return log_oom();
30
31 pager_open(arg_pager_flags);
32
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"
42 " Invoke method\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,
55 link,
56 ansi_underline(),
57 ansi_normal(),
58 ansi_highlight(),
59 ansi_normal());
60
61 return 0;
62}
63
64static int verb_help(int argc, char **argv, void *userdata) {
65 return help();
66}
67
68static int parse_argv(int argc, char *argv[]) {
69
70 enum {
71 ARG_VERSION = 0x100,
72 ARG_NO_PAGER,
73 ARG_MORE,
74 ARG_ONEWAY,
75 ARG_JSON,
76 };
77
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 },
85 {},
86 };
87
88 int c, r;
89
90 assert(argc >= 0);
91 assert(argv);
92
93 while ((c = getopt_long(argc, argv, "hj", options, NULL)) >= 0)
94
95 switch (c) {
96
97 case 'h':
98 return help();
99
100 case ARG_VERSION:
101 return version();
102
103 case ARG_NO_PAGER:
104 arg_pager_flags |= PAGER_DISABLE;
105 break;
106
107 case ARG_MORE:
108 arg_method_flags = (arg_method_flags & ~VARLINK_METHOD_ONEWAY) | VARLINK_METHOD_MORE;
109 break;
110
111 case ARG_ONEWAY:
112 arg_method_flags = (arg_method_flags & ~VARLINK_METHOD_MORE) | VARLINK_METHOD_ONEWAY;
113 break;
114
115 case ARG_JSON:
116 r = parse_json_argument(optarg, &arg_json_format_flags);
117 if (r <= 0)
118 return r;
119
120 break;
121
122 case 'j':
123 arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
124 break;
125
126 case '?':
127 return -EINVAL;
128
129 default:
130 assert_not_reached();
131 }
132
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;
136
137 return 1;
138}
139
140static int varlink_connect_auto(Varlink **ret, const char *where) {
141 int r;
142
143 assert(ret);
144 assert(where);
145
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;
148 struct stat st;
149
150 fd = open(where, O_PATH|O_CLOEXEC);
151 if (fd < 0)
152 return log_error_errno(errno, "Failed to open '%s': %m", where);
153
154 if (fstat(fd, &st) < 0)
155 return log_error_errno(errno, "Failed to stat '%s': %m", where);
156
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));
160 if (r < 0)
161 return log_error_errno(r, "Failed to connect to '%s': %m", where);
162
163 return 0;
164 }
165
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 */
169 if (r < 0)
170 return log_error_errno(r, "Failed to spawn '%s' process: %m", where);
171
172 return 0;
173 }
174
175 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unrecognized path '%s' is neither an AF_UNIX socket, nor an executable binary.", where);
176 }
177
178 /* Otherwise assume this is an URL */
179 r = varlink_connect_url(ret, where);
180 if (r < 0)
181 return log_error_errno(r, "Failed to connect to URL '%s': %m", where);
182
183 return 0;
184}
185
186typedef struct GetInfoData {
187 const char *vendor;
188 const char *product;
189 const char *version;
190 const char *url;
191 char **interfaces;
192} GetInfoData;
193
194static void get_info_data_done(GetInfoData *d) {
195 assert(d);
196
197 d->interfaces = strv_free(d->interfaces);
198}
199
200static int verb_info(int argc, char *argv[], void *userdata) {
201 _cleanup_(varlink_unrefp) Varlink *vl = NULL;
202 const char *url;
203 int r;
204
205 assert(argc == 2);
206 url = argv[1];
207
208 r = varlink_connect_auto(&vl, url);
209 if (r < 0)
210 return r;
211
212 JsonVariant *reply = NULL;
213 const char *error = NULL;
0444391d 214 r = varlink_call(vl, "org.varlink.service.GetInfo", /* parameters= */ NULL, &reply, &error);
d408a53f
LP
215 if (r < 0)
216 return log_error_errno(r, "Failed to issue GetInfo() call: %m");
217 if (error)
218 return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInfo() failed: %s", error);
219
220 pager_open(arg_pager_flags);
221
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 },
229 {}
230 };
231 _cleanup_(get_info_data_done) GetInfoData data = {};
232
f1b622a0 233 r = json_dispatch(reply, dispatch_table, JSON_LOG, &data);
d408a53f
LP
234 if (r < 0)
235 return r;
236
237 strv_sort(data.interfaces);
238
239 if (streq_ptr(argv[0], "list-interfaces")) {
240 STRV_FOREACH(i, data.interfaces)
241 puts(*i);
242 } else {
243 _cleanup_(table_unrefp) Table *t = NULL;
244
245 t = table_new_vertical();
246 if (!t)
247 return log_oom();
248
249 r = table_add_many(
250 t,
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,
257 TABLE_FIELD, "URL",
258 TABLE_STRING, data.url,
259 TABLE_SET_URL, data.url,
260 TABLE_FIELD, "Interfaces",
261 TABLE_STRV, data.interfaces);
262 if (r < 0)
263 return table_log_add_error(r);
264
265 r = table_print(t, NULL);
266 if (r < 0)
267 return table_log_print_error(r);
268 }
269 } else {
270 JsonVariant *v;
271
272 v = streq_ptr(argv[0], "list-interfaces") ?
273 json_variant_by_key(reply, "interfaces") : reply;
274
275 json_variant_dump(v, arg_json_format_flags, stdout, NULL);
276 }
277
278 return 0;
279}
280
281typedef struct GetInterfaceDescriptionData {
282 const char *description;
283} GetInterfaceDescriptionData;
284
285static int verb_introspect(int argc, char *argv[], void *userdata) {
286 _cleanup_(varlink_unrefp) Varlink *vl = NULL;
287 const char *url, *interface;
288 int r;
289
290 assert(argc == 3);
291 url = argv[1];
292 interface = argv[2];
293
294 r = varlink_connect_auto(&vl, url);
295 if (r < 0)
296 return r;
297
298 JsonVariant *reply = NULL;
299 const char *error = NULL;
0444391d 300 r = varlink_callb(vl, "org.varlink.service.GetInterfaceDescription", &reply, &error, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface)));
d408a53f
LP
301 if (r < 0)
302 return log_error_errno(r, "Failed to issue GetInterfaceDescription() call: %m");
303 if (error)
304 return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInterfaceDescription() failed: %s", error);
305
306 pager_open(arg_pager_flags);
307
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 },
311 {}
312 };
313 _cleanup_(varlink_interface_freep) VarlinkInterface *vi = NULL;
314 const char *description = NULL;
315 unsigned line = 0, column = 0;
316
f1b622a0 317 r = json_dispatch(reply, dispatch_table, JSON_LOG, &description);
d408a53f
LP
318 if (r < 0)
319 return r;
320
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);
323 if (r < 0) {
324 log_warning_errno(r, "Failed to parse returned interface description at %u:%u, showing raw interface description: %m", line, column);
325
326 fputs(description, stdout);
327 if (!endswith(description, "\n"))
328 fputs("\n", stdout);
329 } else {
330 r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi);
331 if (r < 0)
332 return log_error_errno(r, "Failed to format parsed interface description: %m");
333 }
334 } else
335 json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
336
337 return 0;
338}
339
340static int reply_callback(
341 Varlink *link,
342 JsonVariant *parameters,
343 const char *error,
344 VarlinkReplyFlags flags,
345 void *userdata) {
346
347 int r;
348
349 assert(link);
350
351 if (error) {
352 /* Propagate the error we received via sd_notify() */
353 (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);
354
355 r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error);
356 } else
357 r = 0;
358
359 json_variant_dump(parameters, arg_json_format_flags, stdout, NULL);
360 return r;
361}
362
363static 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;
368 int r;
369
370 assert(argc >= 3);
371 assert(argc <= 4);
372 url = argv[1];
373 method = argv[2];
374 parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL;
375
376 arg_json_format_flags &= ~JSON_FORMAT_OFF;
377
378 if (parameter) {
698da597 379 /* <argv[4]> is correct, as dispatch_verb() shifts arguments by one for the verb. */
d408a53f
LP
380 r = json_parse_with_source(parameter, "<argv[4]>", 0, &jp, &line, &column);
381 if (r < 0)
382 return log_error_errno(r, "Failed to parse parameters at <argv[4]>:%u:%u: %m", line, column);
383 } else {
384 r = json_parse_file_at(stdin, AT_FDCWD, "<stdin>", 0, &jp, &line, &column);
385 if (r < 0)
386 return log_error_errno(r, "Failed to parse parameters at <stdin>:%u:%u: %m", line, column);
387 }
388
389 r = varlink_connect_auto(&vl, url);
390 if (r < 0)
391 return r;
392
393 if (arg_method_flags & VARLINK_METHOD_ONEWAY) {
394 r = varlink_send(vl, method, jp);
395 if (r < 0)
396 return log_error_errno(r, "Failed to issue %s() call: %m", method);
397
398 r = varlink_flush(vl);
399 if (r < 0)
400 return log_error_errno(r, "Failed to flush Varlink connection: %m");
401
402 } else if (arg_method_flags & VARLINK_METHOD_MORE) {
403
404 varlink_set_userdata(vl, (void*) method);
405
406 r = varlink_bind_reply(vl, reply_callback);
407 if (r < 0)
408 return log_error_errno(r, "Failed to bind reply callback: %m");
409
410 r = varlink_observe(vl, method, jp);
411 if (r < 0)
412 return log_error_errno(r, "Failed to issue %s() call: %m", method);
413
414 for (;;) {
415 r = varlink_is_idle(vl);
416 if (r < 0)
417 return log_error_errno(r, "Failed to check if varlink connection is idle: %m");
418 if (r > 0)
419 break;
420
421 r = varlink_process(vl);
422 if (r < 0)
423 return log_error_errno(r, "Failed to process varlink connection: %m");
424 if (r != 0)
425 continue;
426
427 r = varlink_wait(vl, USEC_INFINITY);
428 if (r < 0)
429 return log_error_errno(r, "Failed to wait for varlink connection events: %m");
430 }
431 } else {
432 JsonVariant *reply = NULL;
433 const char *error = NULL;
434
0444391d 435 r = varlink_call(vl, method, jp, &reply, &error);
d408a53f
LP
436 if (r < 0)
437 return log_error_errno(r, "Failed to issue %s() call: %m", method);
438
439 /* If the server returned an error to us, then fail, but first output the associated parameters */
440 if (error) {
441 /* Propagate the error we received via sd_notify() */
442 (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);
443
444 r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error);
445 } else
446 r = 0;
447
448 pager_open(arg_pager_flags);
449
450 json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
451 return r;
452 }
453
454 return 0;
455}
456
457static int verb_validate_idl(int argc, char *argv[], void *userdata) {
458 _cleanup_(varlink_interface_freep) VarlinkInterface *vi = NULL;
459 _cleanup_free_ char *text = NULL;
460 const char *fname;
461 unsigned line = 1, column = 1;
462 int r;
463
464 fname = argc > 1 ? argv[1] : NULL;
465
466 if (fname) {
467 r = read_full_file(fname, &text, NULL);
468 if (r < 0)
469 return log_error_errno(r, "Failed to read interface description file '%s': %m", fname);
470 } else {
471 r = read_full_stream(stdin, &text, NULL);
472 if (r < 0)
473 return log_error_errno(r, "Failed to read interface description from stdin: %m");
474
475 fname = "<stdin>";
476 }
477
478 r = varlink_idl_parse(text, &line, &column, &vi);
76641edf
LP
479 if (r == -EBADMSG)
480 return log_error_errno(r, "%s:%u:%u: Bad syntax.", fname, line, column);
481 if (r == -ENETUNREACH)
482 return log_error_errno(r, "%s:%u:%u: Failed to parse interface description due an unresolved type.", fname, line, column);
d408a53f
LP
483 if (r < 0)
484 return log_error_errno(r, "%s:%u:%u: Failed to parse interface description: %m", fname, line, column);
485
486 r = varlink_idl_consistent(vi, LOG_ERR);
76641edf
LP
487 if (r == -EUCLEAN)
488 return log_error_errno(r, "Interface is inconsistent.");
489 if (r == -ENOTUNIQ)
490 return log_error_errno(r, "Field or symbol not unique in interface.");
d408a53f 491 if (r < 0)
76641edf 492 return log_error_errno(r, "Failed to check interface for consistency: %m");
d408a53f
LP
493
494 pager_open(arg_pager_flags);
495
496 r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi);
497 if (r < 0)
498 return log_error_errno(r, "Failed to format parsed interface description: %m");
499
500 return 0;
501}
502
503static int varlinkctl_main(int argc, char *argv[]) {
504 static const Verb verbs[] = {
505 { "info", 2, 2, 0, verb_info },
506 { "list-interfaces", 2, 2, 0, verb_info },
507 { "introspect", 3, 3, 0, verb_introspect },
508 { "call", 3, 4, 0, verb_call },
509 { "validate-idl", 1, 2, 0, verb_validate_idl },
510 { "help", VERB_ANY, VERB_ANY, 0, verb_help },
511 {}
512 };
513
514 return dispatch_verb(argc, argv, verbs, NULL);
515}
516
517static int run(int argc, char *argv[]) {
518 int r;
519
520 log_setup();
521
522 r = parse_argv(argc, argv);
523 if (r <= 0)
524 return r;
525
526 return varlinkctl_main(argc, argv);
527}
528
529DEFINE_MAIN_FUNCTION(run);