systemadm: add a wrappable label and use it for status lines
[systemd_ALT/systemd_imz.git] / src / hostnamed.c
blob9e8825da75599e3db0b27421d37910fe424c0fa1
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
3 /***
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
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 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
22 #include <dbus/dbus.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <dlfcn.h>
29 #include "util.h"
30 #include "strv.h"
31 #include "dbus-common.h"
32 #include "polkit.h"
33 #include "def.h"
35 #define INTERFACE \
36 " <interface name=\"org.freedesktop.hostname1\">\n" \
37 " <property name=\"Hostname\" type=\"s\" access=\"read\"/>\n" \
38 " <property name=\"StaticHostname\" type=\"s\" access=\"read\"/>\n" \
39 " <property name=\"PrettyHostname\" type=\"s\" access=\"read\"/>\n" \
40 " <property name=\"IconName\" type=\"s\" access=\"read\"/>\n" \
41 " <method name=\"SetHostname\">\n" \
42 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
43 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
44 " </method>\n" \
45 " <method name=\"SetStaticHostname\">\n" \
46 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
47 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
48 " </method>\n" \
49 " <method name=\"SetPrettyHostname\">\n" \
50 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
51 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
52 " </method>\n" \
53 " <method name=\"SetIconName\">\n" \
54 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
55 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
56 " </method>\n" \
57 " </interface>\n"
59 #define INTROSPECTION \
60 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
61 "<node>\n" \
62 INTERFACE \
63 BUS_PROPERTIES_INTERFACE \
64 BUS_INTROSPECTABLE_INTERFACE \
65 BUS_PEER_INTERFACE \
66 "</node>\n"
68 #define INTERFACES_LIST \
69 BUS_GENERIC_INTERFACES_LIST \
70 "org.freedesktop.hostname1\0"
72 const char hostname_interface[] _introspect_("hostname1") = INTERFACE;
74 enum {
75 PROP_HOSTNAME,
76 PROP_STATIC_HOSTNAME,
77 PROP_PRETTY_HOSTNAME,
78 PROP_ICON_NAME,
79 _PROP_MAX
82 static char *data[_PROP_MAX] = {
83 NULL,
84 NULL,
85 NULL,
86 NULL
89 static usec_t remain_until = 0;
91 static void free_data(void) {
92 int p;
94 for (p = 0; p < _PROP_MAX; p++) {
95 free(data[p]);
96 data[p] = NULL;
100 static int read_data(void) {
101 int r;
103 free_data();
105 data[PROP_HOSTNAME] = gethostname_malloc();
106 if (!data[PROP_HOSTNAME])
107 return -ENOMEM;
109 r = read_one_line_file("/etc/hostname", &data[PROP_STATIC_HOSTNAME]);
110 if (r < 0 && r != -ENOENT)
111 return r;
113 r = parse_env_file("/etc/machine-info", NEWLINE,
114 "PRETTY_HOSTNAME", &data[PROP_PRETTY_HOSTNAME],
115 "ICON_NAME", &data[PROP_ICON_NAME],
116 NULL);
117 if (r < 0 && r != -ENOENT)
118 return r;
120 return 0;
123 static bool check_nss(void) {
125 void *dl;
127 if ((dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY))) {
128 dlclose(dl);
129 return true;
132 return false;
135 static const char* fallback_icon_name(void) {
137 #if defined(__i386__) || defined(__x86_64__)
138 int r;
139 char *type;
140 unsigned t;
141 #endif
143 if (detect_virtualization(NULL) > 0)
144 return "computer-vm";
146 #if defined(__i386__) || defined(__x86_64__)
147 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
148 if (r < 0)
149 return NULL;
151 r = safe_atou(type, &t);
152 free(type);
154 if (r < 0)
155 return NULL;
157 /* We only list the really obvious cases here. The DMI data is
158 unreliable enough, so let's not do any additional guesswork
159 on top of that.
161 See the SMBIOS Specification 2.7.1 section 7.4.1 for
162 details about the values listed here:
164 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
167 switch (t) {
169 case 0x3:
170 case 0x4:
171 case 0x6:
172 case 0x7:
173 return "computer-desktop";
175 case 0x9:
176 case 0xA:
177 case 0xE:
178 return "computer-laptop";
180 case 0x11:
181 case 0x1C:
182 return "computer-server";
185 #endif
186 return NULL;
189 static int write_data_hostname(void) {
190 const char *hn;
192 if (isempty(data[PROP_HOSTNAME]))
193 hn = "localhost";
194 else
195 hn = data[PROP_HOSTNAME];
197 if (sethostname(hn, strlen(hn)) < 0)
198 return -errno;
200 return 0;
203 static int write_data_static_hostname(void) {
205 if (isempty(data[PROP_STATIC_HOSTNAME])) {
207 if (unlink("/etc/hostname") < 0)
208 return errno == ENOENT ? 0 : -errno;
210 return 0;
213 return write_one_line_file_atomic("/etc/hostname", data[PROP_STATIC_HOSTNAME]);
216 static int write_data_other(void) {
218 static const char * const name[_PROP_MAX] = {
219 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
220 [PROP_ICON_NAME] = "ICON_NAME"
223 char **l = NULL;
224 int r, p;
226 r = load_env_file("/etc/machine-info", &l);
227 if (r < 0 && r != -ENOENT)
228 return r;
230 for (p = 2; p < _PROP_MAX; p++) {
231 char *t, **u;
233 assert(name[p]);
235 if (isempty(data[p])) {
236 l = strv_env_unset(l, name[p]);
237 continue;
240 if (asprintf(&t, "%s=%s", name[p], strempty(data[p])) < 0) {
241 strv_free(l);
242 return -ENOMEM;
245 u = strv_env_set(l, t);
246 free(t);
247 strv_free(l);
249 if (!u)
250 return -ENOMEM;
251 l = u;
254 if (strv_isempty(l)) {
256 if (unlink("/etc/machine-info") < 0)
257 return errno == ENOENT ? 0 : -errno;
259 return 0;
262 r = write_env_file("/etc/machine-info", l);
263 strv_free(l);
265 return r;
268 static int bus_hostname_append_icon_name(DBusMessageIter *i, const char *property, void *userdata) {
269 const char *name;
271 assert(i);
272 assert(property);
274 if (isempty(data[PROP_ICON_NAME]))
275 name = fallback_icon_name();
276 else
277 name = data[PROP_ICON_NAME];
279 return bus_property_append_string(i, property, (void*) name);
282 static DBusHandlerResult hostname_message_handler(
283 DBusConnection *connection,
284 DBusMessage *message,
285 void *userdata) {
287 const BusProperty properties[] = {
288 { "org.freedesktop.hostname1", "Hostname", bus_property_append_string, "s", data[PROP_HOSTNAME]},
289 { "org.freedesktop.hostname1", "StaticHostname", bus_property_append_string, "s", data[PROP_STATIC_HOSTNAME]},
290 { "org.freedesktop.hostname1", "PrettyHostname", bus_property_append_string, "s", data[PROP_PRETTY_HOSTNAME]},
291 { "org.freedesktop.hostname1", "IconName", bus_hostname_append_icon_name, "s", data[PROP_ICON_NAME]},
292 { NULL, NULL, NULL, NULL, NULL }
295 DBusMessage *reply = NULL, *changed = NULL;
296 DBusError error;
297 int r;
299 assert(connection);
300 assert(message);
302 dbus_error_init(&error);
304 if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) {
305 const char *name;
306 dbus_bool_t interactive;
308 if (!dbus_message_get_args(
309 message,
310 &error,
311 DBUS_TYPE_STRING, &name,
312 DBUS_TYPE_BOOLEAN, &interactive,
313 DBUS_TYPE_INVALID))
314 return bus_send_error_reply(connection, message, &error, -EINVAL);
316 if (isempty(name))
317 name = data[PROP_STATIC_HOSTNAME];
319 if (isempty(name))
320 name = "localhost";
322 if (!hostname_is_valid(name))
323 return bus_send_error_reply(connection, message, NULL, -EINVAL);
325 if (!streq_ptr(name, data[PROP_HOSTNAME])) {
326 char *h;
328 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, &error);
329 if (r < 0)
330 return bus_send_error_reply(connection, message, &error, r);
332 h = strdup(name);
333 if (!h)
334 goto oom;
336 free(data[PROP_HOSTNAME]);
337 data[PROP_HOSTNAME] = h;
339 r = write_data_hostname();
340 if (r < 0) {
341 log_error("Failed to set host name: %s", strerror(-r));
342 return bus_send_error_reply(connection, message, NULL, r);
345 log_info("Changed host name to '%s'", strempty(data[PROP_HOSTNAME]));
347 changed = bus_properties_changed_new(
348 "/org/freedesktop/hostname1",
349 "org.freedesktop.hostname1",
350 "Hostname\0");
351 if (!changed)
352 goto oom;
355 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) {
356 const char *name;
357 dbus_bool_t interactive;
359 if (!dbus_message_get_args(
360 message,
361 &error,
362 DBUS_TYPE_STRING, &name,
363 DBUS_TYPE_BOOLEAN, &interactive,
364 DBUS_TYPE_INVALID))
365 return bus_send_error_reply(connection, message, &error, -EINVAL);
367 if (isempty(name))
368 name = NULL;
370 if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) {
372 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, &error);
373 if (r < 0)
374 return bus_send_error_reply(connection, message, &error, r);
376 if (isempty(name)) {
377 free(data[PROP_STATIC_HOSTNAME]);
378 data[PROP_STATIC_HOSTNAME] = NULL;
379 } else {
380 char *h;
382 if (!hostname_is_valid(name))
383 return bus_send_error_reply(connection, message, NULL, -EINVAL);
385 h = strdup(name);
386 if (!h)
387 goto oom;
389 free(data[PROP_STATIC_HOSTNAME]);
390 data[PROP_STATIC_HOSTNAME] = h;
393 r = write_data_static_hostname();
394 if (r < 0) {
395 log_error("Failed to write static host name: %s", strerror(-r));
396 return bus_send_error_reply(connection, message, NULL, r);
399 log_info("Changed static host name to '%s'", strempty(data[PROP_HOSTNAME]));
401 changed = bus_properties_changed_new(
402 "/org/freedesktop/hostname1",
403 "org.freedesktop.hostname1",
404 "StaticHostname\0");
405 if (!changed)
406 goto oom;
409 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") ||
410 dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName")) {
412 const char *name;
413 dbus_bool_t interactive;
414 int k;
416 if (!dbus_message_get_args(
417 message,
418 &error,
419 DBUS_TYPE_STRING, &name,
420 DBUS_TYPE_BOOLEAN, &interactive,
421 DBUS_TYPE_INVALID))
422 return bus_send_error_reply(connection, message, &error, -EINVAL);
424 if (isempty(name))
425 name = NULL;
427 k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME : PROP_ICON_NAME;
429 if (!streq_ptr(name, data[k])) {
431 /* Since the pretty hostname should always be
432 * changed at the same time as the static one,
433 * use the same policy action for both... */
435 r = verify_polkit(connection, message, k == PROP_PRETTY_HOSTNAME ?
436 "org.freedesktop.hostname1.set-static-hostname" :
437 "org.freedesktop.hostname1.set-machine-info", interactive, &error);
438 if (r < 0)
439 return bus_send_error_reply(connection, message, &error, r);
441 if (isempty(name)) {
442 free(data[k]);
443 data[k] = NULL;
444 } else {
445 char *h;
447 h = strdup(name);
448 if (!h)
449 goto oom;
451 free(data[k]);
452 data[k] = h;
455 r = write_data_other();
456 if (r < 0) {
457 log_error("Failed to write machine info: %s", strerror(-r));
458 return bus_send_error_reply(connection, message, NULL, r);
461 log_info("Changed %s to '%s'", k == PROP_PRETTY_HOSTNAME ? "pretty host name" : "icon name", strempty(data[k]));
463 changed = bus_properties_changed_new(
464 "/org/freedesktop/hostname1",
465 "org.freedesktop.hostname1",
466 k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" : "IconName\0");
467 if (!changed)
468 goto oom;
471 } else
472 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, properties);
474 if (!(reply = dbus_message_new_method_return(message)))
475 goto oom;
477 if (!dbus_connection_send(connection, reply, NULL))
478 goto oom;
480 dbus_message_unref(reply);
481 reply = NULL;
483 if (changed) {
485 if (!dbus_connection_send(connection, changed, NULL))
486 goto oom;
488 dbus_message_unref(changed);
491 return DBUS_HANDLER_RESULT_HANDLED;
493 oom:
494 if (reply)
495 dbus_message_unref(reply);
497 if (changed)
498 dbus_message_unref(changed);
500 dbus_error_free(&error);
502 return DBUS_HANDLER_RESULT_NEED_MEMORY;
505 static int connect_bus(DBusConnection **_bus) {
506 static const DBusObjectPathVTable hostname_vtable = {
507 .message_function = hostname_message_handler
509 DBusError error;
510 DBusConnection *bus = NULL;
511 int r;
513 assert(_bus);
515 dbus_error_init(&error);
517 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
518 if (!bus) {
519 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
520 r = -ECONNREFUSED;
521 goto fail;
524 dbus_connection_set_exit_on_disconnect(bus, FALSE);
526 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL) ||
527 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
528 log_error("Not enough memory");
529 r = -ENOMEM;
530 goto fail;
533 r = dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
534 if (dbus_error_is_set(&error)) {
535 log_error("Failed to register name on bus: %s", bus_error_message(&error));
536 r = -EEXIST;
537 goto fail;
540 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
541 log_error("Failed to acquire name.");
542 r = -EEXIST;
543 goto fail;
546 if (_bus)
547 *_bus = bus;
549 return 0;
551 fail:
552 dbus_connection_close(bus);
553 dbus_connection_unref(bus);
555 dbus_error_free(&error);
557 return r;
560 int main(int argc, char *argv[]) {
561 int r;
562 DBusConnection *bus = NULL;
563 bool exiting = false;
565 log_set_target(LOG_TARGET_AUTO);
566 log_parse_environment();
567 log_open();
569 umask(0022);
571 if (argc == 2 && streq(argv[1], "--introspect")) {
572 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
573 "<node>\n", stdout);
574 fputs(hostname_interface, stdout);
575 fputs("</node>\n", stdout);
576 return 0;
579 if (argc != 1) {
580 log_error("This program takes no arguments.");
581 r = -EINVAL;
582 goto finish;
585 if (!check_nss())
586 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
588 r = read_data();
589 if (r < 0) {
590 log_error("Failed to read hostname data: %s", strerror(-r));
591 goto finish;
594 r = connect_bus(&bus);
595 if (r < 0)
596 goto finish;
598 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
599 for (;;) {
601 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
602 break;
604 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
605 exiting = true;
606 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
610 r = 0;
612 finish:
613 free_data();
615 if (bus) {
616 dbus_connection_flush(bus);
617 dbus_connection_close(bus);
618 dbus_connection_unref(bus);
621 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;