chiark / gitweb /
hostnamed: make chassis type configurable via /etc/machine-info
[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(DISTRIBUTION);
410                         puts(SYSTEMD_FEATURES);
411                         return 0;
412
413                 case 'P':
414                         arg_transport = TRANSPORT_POLKIT;
415                         break;
416
417                 case 'H':
418                         arg_transport = TRANSPORT_SSH;
419                         arg_host = optarg;
420                         break;
421
422                 case ARG_SET_TRANSIENT:
423                         arg_set_transient = true;
424                         break;
425
426                 case ARG_SET_PRETTY:
427                         arg_set_pretty = true;
428                         break;
429
430                 case ARG_SET_STATIC:
431                         arg_set_static = true;
432                         break;
433
434                 case ARG_NO_ASK_PASSWORD:
435                         arg_ask_password = false;
436                         break;
437
438                 case '?':
439                         return -EINVAL;
440
441                 default:
442                         log_error("Unknown option code %c", c);
443                         return -EINVAL;
444                 }
445         }
446
447         if (!arg_set_transient && !arg_set_pretty && !arg_set_static)
448                 arg_set_transient = arg_set_pretty = arg_set_static = true;
449
450         return 1;
451 }
452
453 static int hostnamectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
454
455         static const struct {
456                 const char* verb;
457                 const enum {
458                         MORE,
459                         LESS,
460                         EQUAL
461                 } argc_cmp;
462                 const int argc;
463                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
464         } verbs[] = {
465                 { "status",        LESS,  1, show_status   },
466                 { "set-hostname",  EQUAL, 2, set_hostname  },
467                 { "set-icon-name", EQUAL, 2, set_icon_name },
468                 { "set-chassis",   EQUAL, 2, set_chassis   },
469         };
470
471         int left;
472         unsigned i;
473
474         assert(argc >= 0);
475         assert(argv);
476         assert(error);
477
478         left = argc - optind;
479
480         if (left <= 0)
481                 /* Special rule: no arguments means "status" */
482                 i = 0;
483         else {
484                 if (streq(argv[optind], "help")) {
485                         help();
486                         return 0;
487                 }
488
489                 for (i = 0; i < ELEMENTSOF(verbs); i++)
490                         if (streq(argv[optind], verbs[i].verb))
491                                 break;
492
493                 if (i >= ELEMENTSOF(verbs)) {
494                         log_error("Unknown operation %s", argv[optind]);
495                         return -EINVAL;
496                 }
497         }
498
499         switch (verbs[i].argc_cmp) {
500
501         case EQUAL:
502                 if (left != verbs[i].argc) {
503                         log_error("Invalid number of arguments.");
504                         return -EINVAL;
505                 }
506
507                 break;
508
509         case MORE:
510                 if (left < verbs[i].argc) {
511                         log_error("Too few arguments.");
512                         return -EINVAL;
513                 }
514
515                 break;
516
517         case LESS:
518                 if (left > verbs[i].argc) {
519                         log_error("Too many arguments.");
520                         return -EINVAL;
521                 }
522
523                 break;
524
525         default:
526                 assert_not_reached("Unknown comparison operator.");
527         }
528
529         if (!bus) {
530                 log_error("Failed to get D-Bus connection: %s", error->message);
531                 return -EIO;
532         }
533
534         return verbs[i].dispatch(bus, argv + optind, left);
535 }
536
537 int main(int argc, char *argv[]) {
538         int r, retval = EXIT_FAILURE;
539         DBusConnection *bus = NULL;
540         DBusError error;
541
542         dbus_error_init(&error);
543
544         setlocale(LC_ALL, "");
545         log_parse_environment();
546         log_open();
547
548         r = parse_argv(argc, argv);
549         if (r < 0)
550                 goto finish;
551         else if (r == 0) {
552                 retval = EXIT_SUCCESS;
553                 goto finish;
554         }
555
556         if (arg_transport == TRANSPORT_NORMAL)
557                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
558         else if (arg_transport == TRANSPORT_POLKIT)
559                 bus_connect_system_polkit(&bus, &error);
560         else if (arg_transport == TRANSPORT_SSH)
561                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
562         else
563                 assert_not_reached("Uh, invalid transport...");
564
565         r = hostnamectl_main(bus, argc, argv, &error);
566         retval = r < 0 ? EXIT_FAILURE : r;
567
568 finish:
569         if (bus) {
570                 dbus_connection_flush(bus);
571                 dbus_connection_close(bus);
572                 dbus_connection_unref(bus);
573         }
574
575         dbus_error_free(&error);
576         dbus_shutdown();
577
578         return retval;
579 }