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