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