chiark / gitweb /
enable localization for common *ctl commands
[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 } StatusInfo;
67
68 static void print_status_info(StatusInfo *i) {
69         sd_id128_t mid, bid;
70         int r;
71         const char *id = NULL;
72         _cleanup_free_ char *pretty_name = NULL, *cpe_name = NULL;
73         struct utsname u;
74
75         assert(i);
76
77         printf("   Static hostname: %s\n",
78                strna(i->static_hostname));
79
80         if (!streq_ptr(i->hostname, i->static_hostname))
81                 printf("Transient hostname: %s\n",
82                        strna(i->hostname));
83
84         printf("   Pretty hostname: %s\n"
85                "         Icon name: %s\n",
86                strna(i->pretty_hostname),
87                strna(i->icon_name));
88
89         r = sd_id128_get_machine(&mid);
90         if (r >= 0)
91                 printf("        Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(mid));
92
93         r = sd_id128_get_boot(&bid);
94         if (r >= 0)
95                 printf("           Boot ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(bid));
96
97         if (detect_virtualization(&id) > 0)
98                 printf("    Virtualization: %s\n", id);
99
100         r = parse_env_file("/etc/os-release", NEWLINE,
101                            "PRETTY_NAME", &pretty_name,
102                            "CPE_NAME", &cpe_name,
103                            NULL);
104
105         if (!isempty(pretty_name))
106                 printf("  Operating System: %s\n", pretty_name);
107
108         if (!isempty(cpe_name))
109                 printf("       CPE OS Name: %s\n", cpe_name);
110
111         assert_se(uname(&u) >= 0);
112         printf("            Kernel: %s %s\n"
113                "      Architecture: %s\n", u.sysname, u.release, u.machine);
114
115 }
116
117 static int status_property(const char *name, DBusMessageIter *iter, StatusInfo *i) {
118         assert(name);
119         assert(iter);
120
121         switch (dbus_message_iter_get_arg_type(iter)) {
122
123         case DBUS_TYPE_STRING: {
124                 const char *s;
125
126                 dbus_message_iter_get_basic(iter, &s);
127                 if (!isempty(s)) {
128                         if (streq(name, "Hostname"))
129                                 i->hostname = s;
130                         if (streq(name, "StaticHostname"))
131                                 i->static_hostname = s;
132                         if (streq(name, "PrettyHostname"))
133                                 i->pretty_hostname = s;
134                         if (streq(name, "IconName"))
135                                 i->icon_name = s;
136                 }
137                 break;
138         }
139         }
140
141         return 0;
142 }
143
144 static int show_status(DBusConnection *bus, char **args, unsigned n) {
145         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
146         const char *interface = "";
147         int r;
148         DBusMessageIter iter, sub, sub2, sub3;
149         StatusInfo info;
150
151         assert(args);
152
153         r = bus_method_call_with_reply(
154                         bus,
155                         "org.freedesktop.hostname1",
156                         "/org/freedesktop/hostname1",
157                         "org.freedesktop.DBus.Properties",
158                         "GetAll",
159                         &reply,
160                         NULL,
161                         DBUS_TYPE_STRING, &interface,
162                         DBUS_TYPE_INVALID);
163         if (r < 0)
164                 return r;
165
166         if (!dbus_message_iter_init(reply, &iter) ||
167             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
168             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
169                 log_error("Failed to parse reply.");
170                 return -EIO;
171         }
172
173         zero(info);
174         dbus_message_iter_recurse(&iter, &sub);
175
176         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
177                 const char *name;
178
179                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
180                         log_error("Failed to parse reply.");
181                         return -EIO;
182                 }
183
184                 dbus_message_iter_recurse(&sub, &sub2);
185
186                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
187                         log_error("Failed to parse reply.");
188                         return -EIO;
189                 }
190
191                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
192                         log_error("Failed to parse reply.");
193                         return -EIO;
194                 }
195
196                 dbus_message_iter_recurse(&sub2, &sub3);
197
198                 r = status_property(name, &sub3, &info);
199                 if (r < 0) {
200                         log_error("Failed to parse reply.");
201                         return r;
202                 }
203
204                 dbus_message_iter_next(&sub);
205         }
206
207         print_status_info(&info);
208         return 0;
209 }
210
211 static char* hostname_simplify(char *s) {
212         char *p, *d;
213
214         for (p = s, d = s; *p; p++) {
215                 if ((*p >= 'a' && *p <= 'z') ||
216                     (*p >= '0' && *p <= '9') ||
217                     *p == '-' || *p == '_')
218                         *(d++) = *p;
219                 else if (*p >= 'A' && *p <= 'Z')
220                         *(d++) = *p - 'A' + 'a';
221                 else if (*p == ' ')
222                         *(d++) = '-';
223         }
224
225         *d = 0;
226
227         strshorten(s, HOST_NAME_MAX);
228         return s;
229 }
230
231 static int set_hostname(DBusConnection *bus, char **args, unsigned n) {
232         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
233         dbus_bool_t interactive = true;
234         _cleanup_free_ char *h = NULL;
235         const char *hostname = args[1];
236         int r;
237
238         assert(args);
239         assert(n == 2);
240
241         polkit_agent_open_if_enabled();
242
243         if (arg_set_pretty) {
244                 r = bus_method_call_with_reply(
245                                 bus,
246                                 "org.freedesktop.hostname1",
247                                 "/org/freedesktop/hostname1",
248                                 "org.freedesktop.hostname1",
249                                 "SetPrettyHostname",
250                                 &reply,
251                                 NULL,
252                                 DBUS_TYPE_STRING, &hostname,
253                                 DBUS_TYPE_BOOLEAN, &interactive,
254                                 DBUS_TYPE_INVALID);
255                 if (r < 0)
256                         return r;
257
258                 h = strdup(hostname);
259                 if (!h)
260                         return log_oom();
261
262                 hostname = hostname_simplify(h);
263         }
264
265         if (arg_set_static) {
266                 r = bus_method_call_with_reply(
267                                 bus,
268                                 "org.freedesktop.hostname1",
269                                 "/org/freedesktop/hostname1",
270                                 "org.freedesktop.hostname1",
271                                 "SetStaticHostname",
272                                 &reply,
273                                 NULL,
274                                 DBUS_TYPE_STRING, &hostname,
275                                 DBUS_TYPE_BOOLEAN, &interactive,
276                                 DBUS_TYPE_INVALID);
277
278                 if (r < 0)
279                         return r;
280         }
281
282         if (arg_set_transient) {
283                 r = bus_method_call_with_reply(
284                                 bus,
285                                 "org.freedesktop.hostname1",
286                                 "/org/freedesktop/hostname1",
287                                 "org.freedesktop.hostname1",
288                                 "SetHostname",
289                                 &reply,
290                                 NULL,
291                                 DBUS_TYPE_STRING, &hostname,
292                                 DBUS_TYPE_BOOLEAN, &interactive,
293                                 DBUS_TYPE_INVALID);
294
295                 if (r < 0)
296                         return r;
297         }
298
299         return 0;
300 }
301
302 static int set_icon_name(DBusConnection *bus, char **args, unsigned n) {
303         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
304         dbus_bool_t interactive = true;
305
306         assert(args);
307         assert(n == 2);
308
309         polkit_agent_open_if_enabled();
310
311         return bus_method_call_with_reply(
312                         bus,
313                         "org.freedesktop.hostname1",
314                         "/org/freedesktop/hostname1",
315                         "org.freedesktop.hostname1",
316                         "SetIconName",
317                         &reply,
318                         NULL,
319                         DBUS_TYPE_STRING, &args[1],
320                         DBUS_TYPE_BOOLEAN, &interactive,
321                         DBUS_TYPE_INVALID);
322 }
323
324 static int help(void) {
325
326         printf("%s [OPTIONS...] COMMAND ...\n\n"
327                "Query or change system hostname.\n\n"
328                "  -h --help              Show this help\n"
329                "     --version           Show package version\n"
330                "     --transient         Only set transient hostname\n"
331                "     --static            Only set static hostname\n"
332                "     --pretty            Only set pretty hostname\n"
333                "     --no-ask-password   Do not prompt for password\n"
334                "  -H --host=[USER@]HOST  Operate on remote host\n\n"
335                "Commands:\n"
336                "  status                 Show current hostname settings\n"
337                "  set-hostname NAME      Set system hostname\n"
338                "  set-icon-name NAME     Set icon name for host\n",
339                program_invocation_short_name);
340
341         return 0;
342 }
343
344 static int parse_argv(int argc, char *argv[]) {
345
346         enum {
347                 ARG_VERSION = 0x100,
348                 ARG_NO_ASK_PASSWORD,
349                 ARG_SET_TRANSIENT,
350                 ARG_SET_STATIC,
351                 ARG_SET_PRETTY
352         };
353
354         static const struct option options[] = {
355                 { "help",            no_argument,       NULL, 'h'                 },
356                 { "version",         no_argument,       NULL, ARG_VERSION         },
357                 { "transient",       no_argument,       NULL, ARG_SET_TRANSIENT   },
358                 { "static",          no_argument,       NULL, ARG_SET_STATIC      },
359                 { "pretty",          no_argument,       NULL, ARG_SET_PRETTY      },
360                 { "host",            required_argument, NULL, 'H'                 },
361                 { "privileged",      no_argument,       NULL, 'P'                 },
362                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
363                 { NULL,              0,                 NULL, 0                   }
364         };
365
366         int c;
367
368         assert(argc >= 0);
369         assert(argv);
370
371         while ((c = getopt_long(argc, argv, "hH:P", options, NULL)) >= 0) {
372
373                 switch (c) {
374
375                 case 'h':
376                         help();
377                         return 0;
378
379                 case ARG_VERSION:
380                         puts(PACKAGE_STRING);
381                         puts(DISTRIBUTION);
382                         puts(SYSTEMD_FEATURES);
383                         return 0;
384
385                 case 'P':
386                         arg_transport = TRANSPORT_POLKIT;
387                         break;
388
389                 case 'H':
390                         arg_transport = TRANSPORT_SSH;
391                         arg_host = optarg;
392                         break;
393
394                 case ARG_SET_TRANSIENT:
395                         arg_set_transient = true;
396                         break;
397
398                 case ARG_SET_PRETTY:
399                         arg_set_pretty = true;
400                         break;
401
402                 case ARG_SET_STATIC:
403                         arg_set_static = true;
404                         break;
405
406                 case '?':
407                         return -EINVAL;
408
409                 default:
410                         log_error("Unknown option code %c", c);
411                         return -EINVAL;
412                 }
413         }
414
415         if (!arg_set_transient && !arg_set_pretty && !arg_set_static)
416                 arg_set_transient = arg_set_pretty = arg_set_static = true;
417
418         return 1;
419 }
420
421 static int hostnamectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
422
423         static const struct {
424                 const char* verb;
425                 const enum {
426                         MORE,
427                         LESS,
428                         EQUAL
429                 } argc_cmp;
430                 const int argc;
431                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
432         } verbs[] = {
433                 { "status",              LESS,   1, show_status         },
434                 { "set-hostname",        EQUAL,  2, set_hostname        },
435                 { "set-icon-name",       EQUAL,  2, set_icon_name       },
436         };
437
438         int left;
439         unsigned i;
440
441         assert(argc >= 0);
442         assert(argv);
443         assert(error);
444
445         left = argc - optind;
446
447         if (left <= 0)
448                 /* Special rule: no arguments means "status" */
449                 i = 0;
450         else {
451                 if (streq(argv[optind], "help")) {
452                         help();
453                         return 0;
454                 }
455
456                 for (i = 0; i < ELEMENTSOF(verbs); i++)
457                         if (streq(argv[optind], verbs[i].verb))
458                                 break;
459
460                 if (i >= ELEMENTSOF(verbs)) {
461                         log_error("Unknown operation %s", argv[optind]);
462                         return -EINVAL;
463                 }
464         }
465
466         switch (verbs[i].argc_cmp) {
467
468         case EQUAL:
469                 if (left != verbs[i].argc) {
470                         log_error("Invalid number of arguments.");
471                         return -EINVAL;
472                 }
473
474                 break;
475
476         case MORE:
477                 if (left < verbs[i].argc) {
478                         log_error("Too few arguments.");
479                         return -EINVAL;
480                 }
481
482                 break;
483
484         case LESS:
485                 if (left > verbs[i].argc) {
486                         log_error("Too many arguments.");
487                         return -EINVAL;
488                 }
489
490                 break;
491
492         default:
493                 assert_not_reached("Unknown comparison operator.");
494         }
495
496         if (!bus) {
497                 log_error("Failed to get D-Bus connection: %s", error->message);
498                 return -EIO;
499         }
500
501         return verbs[i].dispatch(bus, argv + optind, left);
502 }
503
504 int main(int argc, char *argv[]) {
505         int r, retval = EXIT_FAILURE;
506         DBusConnection *bus = NULL;
507         DBusError error;
508
509         dbus_error_init(&error);
510
511         setlocale(LC_ALL, "");
512         log_parse_environment();
513         log_open();
514
515         r = parse_argv(argc, argv);
516         if (r < 0)
517                 goto finish;
518         else if (r == 0) {
519                 retval = EXIT_SUCCESS;
520                 goto finish;
521         }
522
523         if (arg_transport == TRANSPORT_NORMAL)
524                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
525         else if (arg_transport == TRANSPORT_POLKIT)
526                 bus_connect_system_polkit(&bus, &error);
527         else if (arg_transport == TRANSPORT_SSH)
528                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
529         else
530                 assert_not_reached("Uh, invalid transport...");
531
532         r = hostnamectl_main(bus, argc, argv, &error);
533         retval = r < 0 ? EXIT_FAILURE : r;
534
535 finish:
536         if (bus) {
537                 dbus_connection_flush(bus);
538                 dbus_connection_close(bus);
539                 dbus_connection_unref(bus);
540         }
541
542         dbus_error_free(&error);
543         dbus_shutdown();
544
545         return retval;
546 }