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