chiark / gitweb /
Use initalization instead of explicit zeroing
[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 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                 const char *p;
250
251                 /* If the passed hostname is already valid, then
252                  * assume the user doesn't know anything about pretty
253                  * hostnames, so let's unset the pretty hostname, and
254                  * just set the passed hostname as static/dynamic
255                  * hostname. */
256
257                 if (hostname_is_valid(hostname))
258                         p = "";
259                 else {
260                         p = hostname;
261
262                         h = strdup(hostname);
263                         if (!h)
264                                 return log_oom();
265
266                         hostname = hostname_simplify(h);
267                 }
268
269                 r = bus_method_call_with_reply(
270                                 bus,
271                                 "org.freedesktop.hostname1",
272                                 "/org/freedesktop/hostname1",
273                                 "org.freedesktop.hostname1",
274                                 "SetPrettyHostname",
275                                 &reply,
276                                 NULL,
277                                 DBUS_TYPE_STRING, &p,
278                                 DBUS_TYPE_BOOLEAN, &interactive,
279                                 DBUS_TYPE_INVALID);
280                 if (r < 0)
281                         return r;
282
283                 dbus_message_unref(reply);
284                 reply = NULL;
285         }
286
287         if (arg_set_static) {
288                 r = bus_method_call_with_reply(
289                                 bus,
290                                 "org.freedesktop.hostname1",
291                                 "/org/freedesktop/hostname1",
292                                 "org.freedesktop.hostname1",
293                                 "SetStaticHostname",
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                 dbus_message_unref(reply);
304                 reply = NULL;
305         }
306
307         if (arg_set_transient) {
308                 r = bus_method_call_with_reply(
309                                 bus,
310                                 "org.freedesktop.hostname1",
311                                 "/org/freedesktop/hostname1",
312                                 "org.freedesktop.hostname1",
313                                 "SetHostname",
314                                 &reply,
315                                 NULL,
316                                 DBUS_TYPE_STRING, &hostname,
317                                 DBUS_TYPE_BOOLEAN, &interactive,
318                                 DBUS_TYPE_INVALID);
319
320                 if (r < 0)
321                         return r;
322         }
323
324         return 0;
325 }
326
327 static int set_icon_name(DBusConnection *bus, char **args, unsigned n) {
328         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
329         dbus_bool_t interactive = true;
330
331         assert(args);
332         assert(n == 2);
333
334         polkit_agent_open_if_enabled();
335
336         return bus_method_call_with_reply(
337                         bus,
338                         "org.freedesktop.hostname1",
339                         "/org/freedesktop/hostname1",
340                         "org.freedesktop.hostname1",
341                         "SetIconName",
342                         &reply,
343                         NULL,
344                         DBUS_TYPE_STRING, &args[1],
345                         DBUS_TYPE_BOOLEAN, &interactive,
346                         DBUS_TYPE_INVALID);
347 }
348
349 static int set_chassis(DBusConnection *bus, char **args, unsigned n) {
350         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
351         dbus_bool_t interactive = true;
352
353         assert(args);
354         assert(n == 2);
355
356         polkit_agent_open_if_enabled();
357
358         return bus_method_call_with_reply(
359                         bus,
360                         "org.freedesktop.hostname1",
361                         "/org/freedesktop/hostname1",
362                         "org.freedesktop.hostname1",
363                         "SetChassis",
364                         &reply,
365                         NULL,
366                         DBUS_TYPE_STRING, &args[1],
367                         DBUS_TYPE_BOOLEAN, &interactive,
368                         DBUS_TYPE_INVALID);
369 }
370
371 static int help(void) {
372
373         printf("%s [OPTIONS...] COMMAND ...\n\n"
374                "Query or change system hostname.\n\n"
375                "  -h --help              Show this help\n"
376                "     --version           Show package version\n"
377                "     --transient         Only set transient hostname\n"
378                "     --static            Only set static hostname\n"
379                "     --pretty            Only set pretty hostname\n"
380                "     --no-ask-password   Do not prompt for password\n"
381                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
382                "Commands:\n"
383                "  status                 Show current hostname settings\n"
384                "  set-hostname NAME      Set system hostname\n"
385                "  set-icon-name NAME     Set icon name for host\n"
386                "  set-chassis NAME       Set chassis type for host\n",
387                program_invocation_short_name);
388
389         return 0;
390 }
391
392 static int parse_argv(int argc, char *argv[]) {
393
394         enum {
395                 ARG_VERSION = 0x100,
396                 ARG_NO_ASK_PASSWORD,
397                 ARG_SET_TRANSIENT,
398                 ARG_SET_STATIC,
399                 ARG_SET_PRETTY
400         };
401
402         static const struct option options[] = {
403                 { "help",            no_argument,       NULL, 'h'                 },
404                 { "version",         no_argument,       NULL, ARG_VERSION         },
405                 { "transient",       no_argument,       NULL, ARG_SET_TRANSIENT   },
406                 { "static",          no_argument,       NULL, ARG_SET_STATIC      },
407                 { "pretty",          no_argument,       NULL, ARG_SET_PRETTY      },
408                 { "host",            required_argument, NULL, 'H'                 },
409                 { "privileged",      no_argument,       NULL, 'P'                 },
410                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
411                 { NULL,              0,                 NULL, 0                   }
412         };
413
414         int c;
415
416         assert(argc >= 0);
417         assert(argv);
418
419         while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
420
421                 switch (c) {
422
423                 case 'h':
424                         help();
425                         return 0;
426
427                 case ARG_VERSION:
428                         puts(PACKAGE_STRING);
429                         puts(SYSTEMD_FEATURES);
430                         return 0;
431
432                 case 'P':
433                         arg_transport = TRANSPORT_POLKIT;
434                         break;
435
436                 case 'H':
437                         arg_transport = TRANSPORT_SSH;
438                         arg_host = optarg;
439                         break;
440
441                 case ARG_SET_TRANSIENT:
442                         arg_set_transient = true;
443                         break;
444
445                 case ARG_SET_PRETTY:
446                         arg_set_pretty = true;
447                         break;
448
449                 case ARG_SET_STATIC:
450                         arg_set_static = true;
451                         break;
452
453                 case ARG_NO_ASK_PASSWORD:
454                         arg_ask_password = false;
455                         break;
456
457                 case '?':
458                         return -EINVAL;
459
460                 default:
461                         log_error("Unknown option code %c", c);
462                         return -EINVAL;
463                 }
464         }
465
466         if (!arg_set_transient && !arg_set_pretty && !arg_set_static)
467                 arg_set_transient = arg_set_pretty = arg_set_static = true;
468
469         return 1;
470 }
471
472 static int hostnamectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
473
474         static const struct {
475                 const char* verb;
476                 const enum {
477                         MORE,
478                         LESS,
479                         EQUAL
480                 } argc_cmp;
481                 const int argc;
482                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
483         } verbs[] = {
484                 { "status",        LESS,  1, show_status   },
485                 { "set-hostname",  EQUAL, 2, set_hostname  },
486                 { "set-icon-name", EQUAL, 2, set_icon_name },
487                 { "set-chassis",   EQUAL, 2, set_chassis   },
488         };
489
490         int left;
491         unsigned i;
492
493         assert(argc >= 0);
494         assert(argv);
495         assert(error);
496
497         left = argc - optind;
498
499         if (left <= 0)
500                 /* Special rule: no arguments means "status" */
501                 i = 0;
502         else {
503                 if (streq(argv[optind], "help")) {
504                         help();
505                         return 0;
506                 }
507
508                 for (i = 0; i < ELEMENTSOF(verbs); i++)
509                         if (streq(argv[optind], verbs[i].verb))
510                                 break;
511
512                 if (i >= ELEMENTSOF(verbs)) {
513                         log_error("Unknown operation %s", argv[optind]);
514                         return -EINVAL;
515                 }
516         }
517
518         switch (verbs[i].argc_cmp) {
519
520         case EQUAL:
521                 if (left != verbs[i].argc) {
522                         log_error("Invalid number of arguments.");
523                         return -EINVAL;
524                 }
525
526                 break;
527
528         case MORE:
529                 if (left < verbs[i].argc) {
530                         log_error("Too few arguments.");
531                         return -EINVAL;
532                 }
533
534                 break;
535
536         case LESS:
537                 if (left > verbs[i].argc) {
538                         log_error("Too many arguments.");
539                         return -EINVAL;
540                 }
541
542                 break;
543
544         default:
545                 assert_not_reached("Unknown comparison operator.");
546         }
547
548         if (!bus) {
549                 log_error("Failed to get D-Bus connection: %s", error->message);
550                 return -EIO;
551         }
552
553         return verbs[i].dispatch(bus, argv + optind, left);
554 }
555
556 int main(int argc, char *argv[]) {
557         int r, retval = EXIT_FAILURE;
558         DBusConnection *bus = NULL;
559         DBusError error;
560
561         dbus_error_init(&error);
562
563         setlocale(LC_ALL, "");
564         log_parse_environment();
565         log_open();
566
567         r = parse_argv(argc, argv);
568         if (r < 0)
569                 goto finish;
570         else if (r == 0) {
571                 retval = EXIT_SUCCESS;
572                 goto finish;
573         }
574
575         if (arg_transport == TRANSPORT_NORMAL)
576                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
577         else if (arg_transport == TRANSPORT_POLKIT)
578                 bus_connect_system_polkit(&bus, &error);
579         else if (arg_transport == TRANSPORT_SSH)
580                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
581         else
582                 assert_not_reached("Uh, invalid transport...");
583
584         r = hostnamectl_main(bus, argc, argv, &error);
585         retval = r < 0 ? EXIT_FAILURE : r;
586
587 finish:
588         if (bus) {
589                 dbus_connection_flush(bus);
590                 dbus_connection_close(bus);
591                 dbus_connection_unref(bus);
592         }
593
594         dbus_error_free(&error);
595         dbus_shutdown();
596
597         return retval;
598 }