]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/varlinkctl/varlinkctl.c
33c85b87ff33a73d803a0f8efd78d4ca2293c9b1
[thirdparty/systemd.git] / src / varlinkctl / varlinkctl.c
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
19 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
20 static PagerFlags arg_pager_flags = 0;
21 static VarlinkMethodFlags arg_method_flags = 0;
22
23 static 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
64 static int verb_help(int argc, char **argv, void *userdata) {
65 return help();
66 }
67
68 static 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
140 static 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
186 typedef struct GetInfoData {
187 const char *vendor;
188 const char *product;
189 const char *version;
190 const char *url;
191 char **interfaces;
192 } GetInfoData;
193
194 static void get_info_data_done(GetInfoData *d) {
195 assert(d);
196
197 d->interfaces = strv_free(d->interfaces);
198 }
199
200 static 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;
214 r = varlink_call(vl, "org.varlink.service.GetInfo", NULL, &reply, &error, NULL);
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
233 r = json_dispatch(reply, dispatch_table, NULL, JSON_LOG, &data);
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
281 typedef struct GetInterfaceDescriptionData {
282 const char *description;
283 } GetInterfaceDescriptionData;
284
285 static 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;
300 r = varlink_callb(vl, "org.varlink.service.GetInterfaceDescription", &reply, &error, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface)));
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
317 r = json_dispatch(reply, dispatch_table, NULL, JSON_LOG, &description);
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
340 static 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
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;
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) {
379 r = json_parse_with_source(parameter, "<argv[4]>", 0, &jp, &line, &column);
380 if (r < 0)
381 return log_error_errno(r, "Failed to parse parameters at <argv[4]>:%u:%u: %m", line, column);
382 } else {
383 r = json_parse_file_at(stdin, AT_FDCWD, "<stdin>", 0, &jp, &line, &column);
384 if (r < 0)
385 return log_error_errno(r, "Failed to parse parameters at <stdin>:%u:%u: %m", line, column);
386 }
387
388 r = varlink_connect_auto(&vl, url);
389 if (r < 0)
390 return r;
391
392 if (arg_method_flags & VARLINK_METHOD_ONEWAY) {
393 r = varlink_send(vl, method, jp);
394 if (r < 0)
395 return log_error_errno(r, "Failed to issue %s() call: %m", method);
396
397 r = varlink_flush(vl);
398 if (r < 0)
399 return log_error_errno(r, "Failed to flush Varlink connection: %m");
400
401 } else if (arg_method_flags & VARLINK_METHOD_MORE) {
402
403 varlink_set_userdata(vl, (void*) method);
404
405 r = varlink_bind_reply(vl, reply_callback);
406 if (r < 0)
407 return log_error_errno(r, "Failed to bind reply callback: %m");
408
409 r = varlink_observe(vl, method, jp);
410 if (r < 0)
411 return log_error_errno(r, "Failed to issue %s() call: %m", method);
412
413 for (;;) {
414 r = varlink_is_idle(vl);
415 if (r < 0)
416 return log_error_errno(r, "Failed to check if varlink connection is idle: %m");
417 if (r > 0)
418 break;
419
420 r = varlink_process(vl);
421 if (r < 0)
422 return log_error_errno(r, "Failed to process varlink connection: %m");
423 if (r != 0)
424 continue;
425
426 r = varlink_wait(vl, USEC_INFINITY);
427 if (r < 0)
428 return log_error_errno(r, "Failed to wait for varlink connection events: %m");
429 }
430 } else {
431 JsonVariant *reply = NULL;
432 const char *error = NULL;
433
434 r = varlink_call(vl, method, jp, &reply, &error, NULL);
435 if (r < 0)
436 return log_error_errno(r, "Failed to issue %s() call: %m", method);
437
438 /* If the server returned an error to us, then fail, but first output the associated parameters */
439 if (error) {
440 /* Propagate the error we received via sd_notify() */
441 (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);
442
443 r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error);
444 } else
445 r = 0;
446
447 pager_open(arg_pager_flags);
448
449 json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
450 return r;
451 }
452
453 return 0;
454 }
455
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;
459 const char *fname;
460 unsigned line = 1, column = 1;
461 int r;
462
463 fname = argc > 1 ? argv[1] : NULL;
464
465 if (fname) {
466 r = read_full_file(fname, &text, NULL);
467 if (r < 0)
468 return log_error_errno(r, "Failed to read interface description file '%s': %m", fname);
469 } else {
470 r = read_full_stream(stdin, &text, NULL);
471 if (r < 0)
472 return log_error_errno(r, "Failed to read interface description from stdin: %m");
473
474 fname = "<stdin>";
475 }
476
477 r = varlink_idl_parse(text, &line, &column, &vi);
478 if (r == -EBADMSG)
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);
482 if (r < 0)
483 return log_error_errno(r, "%s:%u:%u: Failed to parse interface description: %m", fname, line, column);
484
485 r = varlink_idl_consistent(vi, LOG_ERR);
486 if (r == -EUCLEAN)
487 return log_error_errno(r, "Interface is inconsistent.");
488 if (r == -ENOTUNIQ)
489 return log_error_errno(r, "Field or symbol not unique in interface.");
490 if (r < 0)
491 return log_error_errno(r, "Failed to check interface for consistency: %m");
492
493 pager_open(arg_pager_flags);
494
495 r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi);
496 if (r < 0)
497 return log_error_errno(r, "Failed to format parsed interface description: %m");
498
499 return 0;
500 }
501
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 },
510 {}
511 };
512
513 return dispatch_verb(argc, argv, verbs, NULL);
514 }
515
516 static int run(int argc, char *argv[]) {
517 int r;
518
519 log_setup();
520
521 r = parse_argv(argc, argv);
522 if (r <= 0)
523 return r;
524
525 return varlinkctl_main(argc, argv);
526 }
527
528 DEFINE_MAIN_FUNCTION(run);