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