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