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