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