]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/hostname/hostnamectl.c
Fix write-only use of a few variables
[thirdparty/systemd.git] / src / hostname / hostnamectl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <locale.h>
27 #include <string.h>
28 #include <sys/timex.h>
29 #include <sys/utsname.h>
30
31 #include "dbus-common.h"
32 #include "util.h"
33 #include "spawn-polkit-agent.h"
34 #include "build.h"
35 #include "hwclock.h"
36 #include "strv.h"
37 #include "sd-id128.h"
38 #include "virt.h"
39 #include "fileio.h"
40
41 static enum transport {
42 TRANSPORT_NORMAL,
43 TRANSPORT_SSH,
44 TRANSPORT_POLKIT
45 } arg_transport = TRANSPORT_NORMAL;
46 static bool arg_ask_password = true;
47 static char *arg_host = NULL;
48 static char *arg_user = NULL;
49 static bool arg_transient = false;
50 static bool arg_pretty = false;
51 static bool arg_static = false;
52
53 static void polkit_agent_open_if_enabled(void) {
54
55 /* Open the polkit agent as a child process if necessary */
56
57 if (!arg_ask_password)
58 return;
59
60 polkit_agent_open();
61 }
62
63 typedef struct StatusInfo {
64 const char *hostname;
65 const char *static_hostname;
66 const char *pretty_hostname;
67 const char *icon_name;
68 const char *chassis;
69 } StatusInfo;
70
71 static void print_status_info(StatusInfo *i) {
72 sd_id128_t mid, bid;
73 int r;
74 const char *id = NULL;
75 _cleanup_free_ char *pretty_name = NULL, *cpe_name = NULL;
76 struct utsname u;
77
78 assert(i);
79
80 printf(" Static hostname: %s\n",
81 strna(i->static_hostname));
82
83 if (!isempty(i->pretty_hostname) &&
84 !streq_ptr(i->pretty_hostname, i->static_hostname))
85 printf(" Pretty hostname: %s\n",
86 strna(i->pretty_hostname));
87
88 if (!isempty(i->hostname) &&
89 !streq_ptr(i->hostname, i->static_hostname))
90 printf("Transient hostname: %s\n",
91 strna(i->hostname));
92
93 printf(" Icon name: %s\n"
94 " Chassis: %s\n",
95 strna(i->icon_name),
96 strna(i->chassis));
97
98 r = sd_id128_get_machine(&mid);
99 if (r >= 0)
100 printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(mid));
101
102 r = sd_id128_get_boot(&bid);
103 if (r >= 0)
104 printf(" Boot ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(bid));
105
106 if (detect_virtualization(&id) > 0)
107 printf(" Virtualization: %s\n", id);
108
109 r = parse_env_file("/etc/os-release", NEWLINE,
110 "PRETTY_NAME", &pretty_name,
111 "CPE_NAME", &cpe_name,
112 NULL);
113 if (r < 0)
114 log_warning("Failed to read /etc/os-release: %s", strerror(-r));
115
116 if (!isempty(pretty_name))
117 printf(" Operating System: %s\n", pretty_name);
118
119 if (!isempty(cpe_name))
120 printf(" CPE OS Name: %s\n", cpe_name);
121
122 assert_se(uname(&u) >= 0);
123 printf(" Kernel: %s %s\n"
124 " Architecture: %s\n", u.sysname, u.release, u.machine);
125
126 }
127
128 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
129 assert(name);
130 assert(iter);
131
132 switch (dbus_message_iter_get_arg_type(iter)) {
133
134 case DBUS_TYPE_STRING: {
135 const char *s;
136
137 dbus_message_iter_get_basic(iter, &s);
138 if (!isempty(s)) {
139 if (streq(name, "Hostname"))
140 i->hostname = s;
141 if (streq(name, "StaticHostname"))
142 i->static_hostname = s;
143 if (streq(name, "PrettyHostname"))
144 i->pretty_hostname = s;
145 if (streq(name, "IconName"))
146 i->icon_name = s;
147 if (streq(name, "Chassis"))
148 i->chassis = s;
149 }
150 break;
151 }
152 }
153
154 return 0;
155 }
156
157 static int show_one_name(DBusConnection *bus, const char* attr) {
158 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
159 const char *interface = "org.freedesktop.hostname1", *s;
160 DBusMessageIter iter, sub;
161 int r;
162
163 r = bus_method_call_with_reply(
164 bus,
165 "org.freedesktop.hostname1",
166 "/org/freedesktop/hostname1",
167 "org.freedesktop.DBus.Properties",
168 "Get",
169 &reply,
170 NULL,
171 DBUS_TYPE_STRING, &interface,
172 DBUS_TYPE_STRING, &attr,
173 DBUS_TYPE_INVALID);
174 if (r < 0)
175 return r;
176
177 if (!dbus_message_iter_init(reply, &iter) ||
178 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
179 log_error("Failed to parse reply.");
180 return -EIO;
181 }
182
183 dbus_message_iter_recurse(&iter, &sub);
184
185 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
186 log_error("Failed to parse reply.");
187 return -EIO;
188 }
189
190 dbus_message_iter_get_basic(&sub, &s);
191 printf("%s\n", s);
192
193 return 0;
194 }
195
196 static int show_all_names(DBusConnection *bus) {
197 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
198 const char *interface = "";
199 int r;
200 DBusMessageIter iter, sub, sub2, sub3;
201 StatusInfo info = {};
202
203 r = bus_method_call_with_reply(
204 bus,
205 "org.freedesktop.hostname1",
206 "/org/freedesktop/hostname1",
207 "org.freedesktop.DBus.Properties",
208 "GetAll",
209 &reply,
210 NULL,
211 DBUS_TYPE_STRING, &interface,
212 DBUS_TYPE_INVALID);
213 if (r < 0)
214 return r;
215
216 if (!dbus_message_iter_init(reply, &iter) ||
217 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
218 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
219 log_error("Failed to parse reply.");
220 return -EIO;
221 }
222
223 dbus_message_iter_recurse(&iter, &sub);
224
225 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
226 const char *name;
227
228 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
229 log_error("Failed to parse reply.");
230 return -EIO;
231 }
232
233 dbus_message_iter_recurse(&sub, &sub2);
234
235 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
236 log_error("Failed to parse reply.");
237 return -EIO;
238 }
239
240 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
241 log_error("Failed to parse reply.");
242 return -EIO;
243 }
244
245 dbus_message_iter_recurse(&sub2, &sub3);
246
247 r = status_property(name, &sub3, &info);
248 if (r < 0) {
249 log_error("Failed to parse reply.");
250 return r;
251 }
252
253 dbus_message_iter_next(&sub);
254 }
255
256 print_status_info(&info);
257 return 0;
258 }
259
260 static int show_status(DBusConnection *bus, char **args, unsigned n) {
261 assert(args);
262
263 if (arg_pretty || arg_static || arg_transient) {
264 const char *attr;
265
266 if (!!arg_static + !!arg_pretty + !!arg_transient > 1) {
267 log_error("Cannot query more than one name type at a time");
268 return -EINVAL;
269 }
270
271 attr = arg_pretty ? "PrettyHostname" :
272 arg_static ? "StaticHostname" : "Hostname";
273
274 return show_one_name(bus, attr);
275 } else
276 return show_all_names(bus);
277 }
278
279 static int set_hostname(DBusConnection *bus, char **args, unsigned n) {
280 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
281 dbus_bool_t interactive = arg_ask_password;
282 _cleanup_free_ char *h = NULL;
283 const char *hostname = args[1];
284 int r;
285
286 assert(args);
287 assert(n == 2);
288
289 polkit_agent_open_if_enabled();
290
291 if (!arg_pretty && !arg_static && !arg_transient)
292 arg_pretty = arg_static = arg_transient = true;
293
294 if (arg_pretty) {
295 const char *p;
296
297 /* If the passed hostname is already valid, then
298 * assume the user doesn't know anything about pretty
299 * hostnames, so let's unset the pretty hostname, and
300 * just set the passed hostname as static/dynamic
301 * hostname. */
302
303 h = strdup(hostname);
304 if (!h)
305 return log_oom();
306
307 hostname_cleanup(h, true);
308
309 if (arg_static && streq(h, hostname))
310 p = "";
311 else {
312 p = hostname;
313 hostname = h;
314 }
315
316 r = bus_method_call_with_reply(
317 bus,
318 "org.freedesktop.hostname1",
319 "/org/freedesktop/hostname1",
320 "org.freedesktop.hostname1",
321 "SetPrettyHostname",
322 &reply,
323 NULL,
324 DBUS_TYPE_STRING, &p,
325 DBUS_TYPE_BOOLEAN, &interactive,
326 DBUS_TYPE_INVALID);
327 if (r < 0)
328 return r;
329
330 dbus_message_unref(reply);
331 reply = NULL;
332 }
333
334 if (arg_static) {
335 r = bus_method_call_with_reply(
336 bus,
337 "org.freedesktop.hostname1",
338 "/org/freedesktop/hostname1",
339 "org.freedesktop.hostname1",
340 "SetStaticHostname",
341 &reply,
342 NULL,
343 DBUS_TYPE_STRING, &hostname,
344 DBUS_TYPE_BOOLEAN, &interactive,
345 DBUS_TYPE_INVALID);
346
347 if (r < 0)
348 return r;
349
350 dbus_message_unref(reply);
351 reply = NULL;
352 }
353
354 if (arg_transient) {
355 r = bus_method_call_with_reply(
356 bus,
357 "org.freedesktop.hostname1",
358 "/org/freedesktop/hostname1",
359 "org.freedesktop.hostname1",
360 "SetHostname",
361 &reply,
362 NULL,
363 DBUS_TYPE_STRING, &hostname,
364 DBUS_TYPE_BOOLEAN, &interactive,
365 DBUS_TYPE_INVALID);
366
367 if (r < 0)
368 return r;
369 }
370
371 return 0;
372 }
373
374 static int set_icon_name(DBusConnection *bus, char **args, unsigned n) {
375 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
376 dbus_bool_t interactive = arg_ask_password;
377
378 assert(args);
379 assert(n == 2);
380
381 polkit_agent_open_if_enabled();
382
383 return bus_method_call_with_reply(
384 bus,
385 "org.freedesktop.hostname1",
386 "/org/freedesktop/hostname1",
387 "org.freedesktop.hostname1",
388 "SetIconName",
389 &reply,
390 NULL,
391 DBUS_TYPE_STRING, &args[1],
392 DBUS_TYPE_BOOLEAN, &interactive,
393 DBUS_TYPE_INVALID);
394 }
395
396 static int set_chassis(DBusConnection *bus, char **args, unsigned n) {
397 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
398 dbus_bool_t interactive = arg_ask_password;
399
400 assert(args);
401 assert(n == 2);
402
403 polkit_agent_open_if_enabled();
404
405 return bus_method_call_with_reply(
406 bus,
407 "org.freedesktop.hostname1",
408 "/org/freedesktop/hostname1",
409 "org.freedesktop.hostname1",
410 "SetChassis",
411 &reply,
412 NULL,
413 DBUS_TYPE_STRING, &args[1],
414 DBUS_TYPE_BOOLEAN, &interactive,
415 DBUS_TYPE_INVALID);
416 }
417
418 static int help(void) {
419
420 printf("%s [OPTIONS...] COMMAND ...\n\n"
421 "Query or change system hostname.\n\n"
422 " -h --help Show this help\n"
423 " --version Show package version\n"
424 " --transient Only set transient hostname\n"
425 " --static Only set static hostname\n"
426 " --pretty Only set pretty hostname\n"
427 " -P --privileged Acquire privileges before execution\n"
428 " --no-ask-password Do not prompt for password\n"
429 " -H --host=[USER@]HOST Operate on remote host\n\n"
430 "Commands:\n"
431 " status Show current hostname settings\n"
432 " set-hostname NAME Set system hostname\n"
433 " set-icon-name NAME Set icon name for host\n"
434 " set-chassis NAME Set chassis type for host\n",
435 program_invocation_short_name);
436
437 return 0;
438 }
439
440 static int parse_argv(int argc, char *argv[]) {
441
442 enum {
443 ARG_VERSION = 0x100,
444 ARG_NO_ASK_PASSWORD,
445 ARG_TRANSIENT,
446 ARG_STATIC,
447 ARG_PRETTY
448 };
449
450 static const struct option options[] = {
451 { "help", no_argument, NULL, 'h' },
452 { "version", no_argument, NULL, ARG_VERSION },
453 { "transient", no_argument, NULL, ARG_TRANSIENT },
454 { "static", no_argument, NULL, ARG_STATIC },
455 { "pretty", no_argument, NULL, ARG_PRETTY },
456 { "host", required_argument, NULL, 'H' },
457 { "privileged", no_argument, NULL, 'P' },
458 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
459 { NULL, 0, NULL, 0 }
460 };
461
462 int c;
463
464 assert(argc >= 0);
465 assert(argv);
466
467 while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
468
469 switch (c) {
470
471 case 'h':
472 help();
473 return 0;
474
475 case ARG_VERSION:
476 puts(PACKAGE_STRING);
477 puts(SYSTEMD_FEATURES);
478 return 0;
479
480 case 'P':
481 arg_transport = TRANSPORT_POLKIT;
482 break;
483
484 case 'H':
485 arg_transport = TRANSPORT_SSH;
486 parse_user_at_host(optarg, &arg_user, &arg_host);
487 break;
488
489 case ARG_TRANSIENT:
490 arg_transient = true;
491 break;
492
493 case ARG_PRETTY:
494 arg_pretty = true;
495 break;
496
497 case ARG_STATIC:
498 arg_static = true;
499 break;
500
501 case ARG_NO_ASK_PASSWORD:
502 arg_ask_password = false;
503 break;
504
505 case '?':
506 return -EINVAL;
507
508 default:
509 log_error("Unknown option code %c", c);
510 return -EINVAL;
511 }
512 }
513
514 return 1;
515 }
516
517 static int hostnamectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
518
519 static const struct {
520 const char* verb;
521 const enum {
522 MORE,
523 LESS,
524 EQUAL
525 } argc_cmp;
526 const int argc;
527 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
528 } verbs[] = {
529 { "status", LESS, 1, show_status },
530 { "set-hostname", EQUAL, 2, set_hostname },
531 { "set-icon-name", EQUAL, 2, set_icon_name },
532 { "set-chassis", EQUAL, 2, set_chassis },
533 };
534
535 int left;
536 unsigned i;
537
538 assert(argc >= 0);
539 assert(argv);
540 assert(error);
541
542 left = argc - optind;
543
544 if (left <= 0)
545 /* Special rule: no arguments means "status" */
546 i = 0;
547 else {
548 if (streq(argv[optind], "help")) {
549 help();
550 return 0;
551 }
552
553 for (i = 0; i < ELEMENTSOF(verbs); i++)
554 if (streq(argv[optind], verbs[i].verb))
555 break;
556
557 if (i >= ELEMENTSOF(verbs)) {
558 log_error("Unknown operation %s", argv[optind]);
559 return -EINVAL;
560 }
561 }
562
563 switch (verbs[i].argc_cmp) {
564
565 case EQUAL:
566 if (left != verbs[i].argc) {
567 log_error("Invalid number of arguments.");
568 return -EINVAL;
569 }
570
571 break;
572
573 case MORE:
574 if (left < verbs[i].argc) {
575 log_error("Too few arguments.");
576 return -EINVAL;
577 }
578
579 break;
580
581 case LESS:
582 if (left > verbs[i].argc) {
583 log_error("Too many arguments.");
584 return -EINVAL;
585 }
586
587 break;
588
589 default:
590 assert_not_reached("Unknown comparison operator.");
591 }
592
593 if (!bus) {
594 log_error("Failed to get D-Bus connection: %s", error->message);
595 return -EIO;
596 }
597
598 return verbs[i].dispatch(bus, argv + optind, left);
599 }
600
601 int main(int argc, char *argv[]) {
602 int r, retval = EXIT_FAILURE;
603 DBusConnection *bus = NULL;
604 DBusError error;
605
606 dbus_error_init(&error);
607
608 setlocale(LC_ALL, "");
609 log_parse_environment();
610 log_open();
611
612 r = parse_argv(argc, argv);
613 if (r < 0)
614 goto finish;
615 else if (r == 0) {
616 retval = EXIT_SUCCESS;
617 goto finish;
618 }
619
620 if (arg_transport == TRANSPORT_NORMAL)
621 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
622 else if (arg_transport == TRANSPORT_POLKIT)
623 bus_connect_system_polkit(&bus, &error);
624 else if (arg_transport == TRANSPORT_SSH)
625 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
626 else
627 assert_not_reached("Uh, invalid transport...");
628
629 r = hostnamectl_main(bus, argc, argv, &error);
630 retval = r < 0 ? EXIT_FAILURE : r;
631
632 finish:
633 if (bus) {
634 dbus_connection_flush(bus);
635 dbus_connection_close(bus);
636 dbus_connection_unref(bus);
637 }
638
639 dbus_error_free(&error);
640 dbus_shutdown();
641
642 return retval;
643 }