chiark / gitweb /
33245dd4f98d4d6e21622454ea40bb64953e8ed0
[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 <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 }