]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/hostname/hostnamectl.c
timedatectl,hostnamectl: rework --help text
[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 <string.h>
27 #include <sys/timex.h>
28
29 #include "dbus-common.h"
30 #include "util.h"
31 #include "spawn-polkit-agent.h"
32 #include "build.h"
33 #include "hwclock.h"
34 #include "strv.h"
35 #include "sd-id128.h"
36 #include "virt.h"
37
38 static enum transport {
39 TRANSPORT_NORMAL,
40 TRANSPORT_SSH,
41 TRANSPORT_POLKIT
42 } arg_transport = TRANSPORT_NORMAL;
43 static bool arg_ask_password = true;
44 static const char *arg_host = NULL;
45 static bool arg_set_transient = false;
46 static bool arg_set_pretty = false;
47 static bool arg_set_static = false;
48
49 static void polkit_agent_open_if_enabled(void) {
50
51 /* Open the polkit agent as a child process if necessary */
52
53 if (!arg_ask_password)
54 return;
55
56 polkit_agent_open();
57 }
58
59 typedef struct StatusInfo {
60 const char *hostname;
61 const char *static_hostname;
62 const char *pretty_hostname;
63 const char *icon_name;
64 } StatusInfo;
65
66 static void print_status_info(StatusInfo *i) {
67 sd_id128_t mid, bid;
68 int r;
69 const char *id;
70
71 assert(i);
72
73 printf(" Static hostname: %s\n",
74 strna(i->static_hostname));
75
76 if (!streq_ptr(i->hostname, i->static_hostname))
77 printf("Transient hostname: %s\n",
78 strna(i->hostname));
79
80 printf(" Pretty hostname: %s\n"
81 " Icon name: %s\n",
82 strna(i->pretty_hostname),
83 strna(i->icon_name));
84
85 r = sd_id128_get_machine(&mid);
86 if (r >= 0)
87 printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(mid));
88
89 r = sd_id128_get_boot(&bid);
90 if (r >= 0)
91 printf(" Boot ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(bid));
92
93 if (detect_virtualization(&id) >= 0)
94 printf(" Virtualization: %s\n", id);
95 }
96
97 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
98 assert(name);
99 assert(iter);
100
101 switch (dbus_message_iter_get_arg_type(iter)) {
102
103 case DBUS_TYPE_STRING: {
104 const char *s;
105
106 dbus_message_iter_get_basic(iter, &s);
107 if (!isempty(s)) {
108 if (streq(name, "Hostname"))
109 i->hostname = s;
110 if (streq(name, "StaticHostname"))
111 i->static_hostname = s;
112 if (streq(name, "PrettyHostname"))
113 i->pretty_hostname = s;
114 if (streq(name, "IconName"))
115 i->icon_name = s;
116 }
117 break;
118 }
119 }
120
121 return 0;
122 }
123
124 static int show_status(DBusConnection *bus, char **args, unsigned n) {
125 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
126 const char *interface = "";
127 int r;
128 DBusMessageIter iter, sub, sub2, sub3;
129 StatusInfo info;
130
131 assert(args);
132
133 r = bus_method_call_with_reply(
134 bus,
135 "org.freedesktop.hostname1",
136 "/org/freedesktop/hostname1",
137 "org.freedesktop.DBus.Properties",
138 "GetAll",
139 &reply,
140 NULL,
141 DBUS_TYPE_STRING, &interface,
142 DBUS_TYPE_INVALID);
143 if (r < 0)
144 return r;
145
146 if (!dbus_message_iter_init(reply, &iter) ||
147 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
148 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
149 log_error("Failed to parse reply.");
150 return -EIO;
151 }
152
153 zero(info);
154 dbus_message_iter_recurse(&iter, &sub);
155
156 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
157 const char *name;
158
159 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
160 log_error("Failed to parse reply.");
161 return -EIO;
162 }
163
164 dbus_message_iter_recurse(&sub, &sub2);
165
166 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
167 log_error("Failed to parse reply.");
168 return -EIO;
169 }
170
171 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
172 log_error("Failed to parse reply.");
173 return -EIO;
174 }
175
176 dbus_message_iter_recurse(&sub2, &sub3);
177
178 r = status_property(name, &sub3, &info);
179 if (r < 0) {
180 log_error("Failed to parse reply.");
181 return r;
182 }
183
184 dbus_message_iter_next(&sub);
185 }
186
187 print_status_info(&info);
188 return 0;
189 }
190
191 static char* hostname_simplify(char *s) {
192 char *p, *d;
193
194 for (p = s, d = s; *p; p++) {
195 if ((*p >= 'a' && *p <= 'z') ||
196 (*p >= '0' && *p <= '9') ||
197 *p == '-' || *p == '_')
198 *(d++) = *p;
199 else if (*p >= 'A' && *p <= 'Z')
200 *(d++) = *p - 'A' + 'a';
201 else if (*p == ' ')
202 *(d++) = '-';
203 }
204
205 *d = 0;
206
207 strshorten(s, HOST_NAME_MAX);
208 return s;
209 }
210
211 static int set_hostname(DBusConnection *bus, char **args, unsigned n) {
212 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
213 dbus_bool_t interactive = true;
214 _cleanup_free_ char *h = NULL;
215 const char *hostname = args[1];
216 int r;
217
218 assert(args);
219 assert(n == 2);
220
221 polkit_agent_open_if_enabled();
222
223 if (arg_set_pretty) {
224 r = bus_method_call_with_reply(
225 bus,
226 "org.freedesktop.hostname1",
227 "/org/freedesktop/hostname1",
228 "org.freedesktop.hostname1",
229 "SetPrettyHostname",
230 &reply,
231 NULL,
232 DBUS_TYPE_STRING, &hostname,
233 DBUS_TYPE_BOOLEAN, &interactive,
234 DBUS_TYPE_INVALID);
235 if (r < 0)
236 return r;
237
238 h = strdup(hostname);
239 if (!h)
240 return log_oom();
241
242 hostname = hostname_simplify(h);
243 }
244
245 if (arg_set_static) {
246 r = bus_method_call_with_reply(
247 bus,
248 "org.freedesktop.hostname1",
249 "/org/freedesktop/hostname1",
250 "org.freedesktop.hostname1",
251 "SetStaticHostname",
252 &reply,
253 NULL,
254 DBUS_TYPE_STRING, &hostname,
255 DBUS_TYPE_BOOLEAN, &interactive,
256 DBUS_TYPE_INVALID);
257
258 if (r < 0)
259 return r;
260 }
261
262 if (arg_set_transient) {
263 r = bus_method_call_with_reply(
264 bus,
265 "org.freedesktop.hostname1",
266 "/org/freedesktop/hostname1",
267 "org.freedesktop.hostname1",
268 "SetHostname",
269 &reply,
270 NULL,
271 DBUS_TYPE_STRING, &hostname,
272 DBUS_TYPE_BOOLEAN, &interactive,
273 DBUS_TYPE_INVALID);
274
275 if (r < 0)
276 return r;
277 }
278
279 return 0;
280 }
281
282 static int set_icon_name(DBusConnection *bus, char **args, unsigned n) {
283 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
284 dbus_bool_t interactive = true;
285
286 assert(args);
287 assert(n == 2);
288
289 polkit_agent_open_if_enabled();
290
291 return bus_method_call_with_reply(
292 bus,
293 "org.freedesktop.hostname1",
294 "/org/freedesktop/hostname1",
295 "org.freedesktop.hostname1",
296 "SetIconName",
297 &reply,
298 NULL,
299 DBUS_TYPE_STRING, &args[1],
300 DBUS_TYPE_BOOLEAN, &interactive,
301 DBUS_TYPE_INVALID);
302 }
303
304 static int help(void) {
305
306 printf("%s [OPTIONS...] COMMAND ...\n\n"
307 "Query or change system hostname.\n\n"
308 " -h --help Show this help\n"
309 " --version Show package version\n"
310 " --transient Only set transient hostname\n"
311 " --static Only set static hostname\n"
312 " --pretty Only set pretty hostname\n"
313 " --no-ask-password Do not prompt for password\n"
314 " -H --host=[USER@]HOST Operate on remote host\n\n"
315 "Commands:\n"
316 " status Show current hostname settings\n"
317 " set-hostname NAME Set system hostname\n"
318 " set-icon-name NAME Set icon name for host\n",
319 program_invocation_short_name);
320
321 return 0;
322 }
323
324 static int parse_argv(int argc, char *argv[]) {
325
326 enum {
327 ARG_VERSION = 0x100,
328 ARG_NO_ASK_PASSWORD,
329 ARG_SET_TRANSIENT,
330 ARG_SET_STATIC,
331 ARG_SET_PRETTY
332 };
333
334 static const struct option options[] = {
335 { "help", no_argument, NULL, 'h' },
336 { "version", no_argument, NULL, ARG_VERSION },
337 { "transient", no_argument, NULL, ARG_SET_TRANSIENT },
338 { "static", no_argument, NULL, ARG_SET_STATIC },
339 { "pretty", no_argument, NULL, ARG_SET_PRETTY },
340 { "host", required_argument, NULL, 'H' },
341 { "privileged", no_argument, NULL, 'P' },
342 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
343 { NULL, 0, NULL, 0 }
344 };
345
346 int c;
347
348 assert(argc >= 0);
349 assert(argv);
350
351 while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
352
353 switch (c) {
354
355 case 'h':
356 help();
357 return 0;
358
359 case ARG_VERSION:
360 puts(PACKAGE_STRING);
361 puts(DISTRIBUTION);
362 puts(SYSTEMD_FEATURES);
363 return 0;
364
365 case 'P':
366 arg_transport = TRANSPORT_POLKIT;
367 break;
368
369 case 'H':
370 arg_transport = TRANSPORT_SSH;
371 arg_host = optarg;
372 break;
373
374 case ARG_SET_TRANSIENT:
375 arg_set_transient = true;
376 break;
377
378 case ARG_SET_PRETTY:
379 arg_set_pretty = true;
380 break;
381
382 case ARG_SET_STATIC:
383 arg_set_static = true;
384 break;
385
386 case '?':
387 return -EINVAL;
388
389 default:
390 log_error("Unknown option code %c", c);
391 return -EINVAL;
392 }
393 }
394
395 if (!arg_set_transient && !arg_set_pretty && !arg_set_static)
396 arg_set_transient = arg_set_pretty = arg_set_static = true;
397
398 return 1;
399 }
400
401 static int hostnamectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
402
403 static const struct {
404 const char* verb;
405 const enum {
406 MORE,
407 LESS,
408 EQUAL
409 } argc_cmp;
410 const int argc;
411 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
412 } verbs[] = {
413 { "status", LESS, 1, show_status },
414 { "set-hostname", LESS, 2, set_hostname },
415 { "set-icon-name", EQUAL, 2, set_icon_name },
416 };
417
418 int left;
419 unsigned i;
420
421 assert(argc >= 0);
422 assert(argv);
423 assert(error);
424
425 left = argc - optind;
426
427 if (left <= 0)
428 /* Special rule: no arguments means "status" */
429 i = 0;
430 else {
431 if (streq(argv[optind], "help")) {
432 help();
433 return 0;
434 }
435
436 for (i = 0; i < ELEMENTSOF(verbs); i++)
437 if (streq(argv[optind], verbs[i].verb))
438 break;
439
440 if (i >= ELEMENTSOF(verbs)) {
441 log_error("Unknown operation %s", argv[optind]);
442 return -EINVAL;
443 }
444 }
445
446 switch (verbs[i].argc_cmp) {
447
448 case EQUAL:
449 if (left != verbs[i].argc) {
450 log_error("Invalid number of arguments.");
451 return -EINVAL;
452 }
453
454 break;
455
456 case MORE:
457 if (left < verbs[i].argc) {
458 log_error("Too few arguments.");
459 return -EINVAL;
460 }
461
462 break;
463
464 case LESS:
465 if (left > verbs[i].argc) {
466 log_error("Too many arguments.");
467 return -EINVAL;
468 }
469
470 break;
471
472 default:
473 assert_not_reached("Unknown comparison operator.");
474 }
475
476 if (!bus) {
477 log_error("Failed to get D-Bus connection: %s", error->message);
478 return -EIO;
479 }
480
481 return verbs[i].dispatch(bus, argv + optind, left);
482 }
483
484 int main(int argc, char *argv[]) {
485 int r, retval = EXIT_FAILURE;
486 DBusConnection *bus = NULL;
487 DBusError error;
488
489 dbus_error_init(&error);
490
491 log_parse_environment();
492 log_open();
493
494 r = parse_argv(argc, argv);
495 if (r < 0)
496 goto finish;
497 else if (r == 0) {
498 retval = EXIT_SUCCESS;
499 goto finish;
500 }
501
502 if (arg_transport == TRANSPORT_NORMAL)
503 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
504 else if (arg_transport == TRANSPORT_POLKIT)
505 bus_connect_system_polkit(&bus, &error);
506 else if (arg_transport == TRANSPORT_SSH)
507 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
508 else
509 assert_not_reached("Uh, invalid transport...");
510
511 r = hostnamectl_main(bus, argc, argv, &error);
512 retval = r < 0 ? EXIT_FAILURE : r;
513
514 finish:
515 if (bus) {
516 dbus_connection_flush(bus);
517 dbus_connection_close(bus);
518 dbus_connection_unref(bus);
519 }
520
521 dbus_error_free(&error);
522 dbus_shutdown();
523
524 return retval;
525 }