1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
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 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.
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.
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/>.
22 #include <dbus/dbus.h>
31 #include "dbus-common.h"
36 #include "fileio-label.h"
40 " <interface name=\"org.freedesktop.hostname1\">\n" \
41 " <property name=\"Hostname\" type=\"s\" access=\"read\"/>\n" \
42 " <property name=\"StaticHostname\" type=\"s\" access=\"read\"/>\n" \
43 " <property name=\"PrettyHostname\" type=\"s\" access=\"read\"/>\n" \
44 " <property name=\"IconName\" type=\"s\" access=\"read\"/>\n" \
45 " <property name=\"Chassis\" type=\"s\" access=\"read\"/>\n" \
46 " <method name=\"SetHostname\">\n" \
47 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
48 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
50 " <method name=\"SetStaticHostname\">\n" \
51 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
52 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
54 " <method name=\"SetPrettyHostname\">\n" \
55 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
56 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
58 " <method name=\"SetIconName\">\n" \
59 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
60 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
62 " <method name=\"SetChassis\">\n" \
63 " <arg name=\"name\" type=\"s\" direction=\"in\"/>\n" \
64 " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
68 #define INTROSPECTION \
69 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
72 BUS_PROPERTIES_INTERFACE \
73 BUS_INTROSPECTABLE_INTERFACE \
77 #define INTERFACES_LIST \
78 BUS_GENERIC_INTERFACES_LIST \
79 "org.freedesktop.hostname1\0"
81 const char hostname_interface[] _introspect_("hostname1") = INTERFACE;
92 static char *data[_PROP_MAX] = {
100 static usec_t remain_until = 0;
102 static void free_data(void) {
105 for (p = 0; p < _PROP_MAX; p++) {
111 static int read_data(void) {
116 data[PROP_HOSTNAME] = gethostname_malloc();
117 if (!data[PROP_HOSTNAME])
120 r = read_one_line_file("/etc/hostname", &data[PROP_STATIC_HOSTNAME]);
121 if (r < 0 && r != -ENOENT)
124 r = parse_env_file("/etc/machine-info", NEWLINE,
125 "PRETTY_HOSTNAME", &data[PROP_PRETTY_HOSTNAME],
126 "ICON_NAME", &data[PROP_ICON_NAME],
127 "CHASSIS", &data[PROP_CHASSIS],
129 if (r < 0 && r != -ENOENT)
135 static bool check_nss(void) {
138 dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
147 static bool valid_chassis(const char *chassis) {
151 return nulstr_contains(
162 static bool pretty_string_is_safe(const char *p) {
167 for (t = p; *t; t++) {
168 if (*t >= '\0' && *t < ' ')
175 static const char* fallback_chassis(void) {
181 v = detect_virtualization(NULL);
183 if (v == VIRTUALIZATION_VM)
185 if (v == VIRTUALIZATION_CONTAINER)
188 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
192 r = safe_atou(type, &t);
197 /* We only list the really obvious cases here as the ACPI data
198 * is not really super reliable.
200 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
202 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
225 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
229 r = safe_atou(type, &t);
234 /* We only list the really obvious cases here. The DMI data is
235 unreliable enough, so let's not do any additional guesswork
238 See the SMBIOS Specification 2.7.1 section 7.4.1 for
239 details about the values listed here:
241 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
269 static char* fallback_icon_name(void) {
272 if (!isempty(data[PROP_CHASSIS]))
273 return strappend("computer-", data[PROP_CHASSIS]);
275 chassis = fallback_chassis();
277 return strappend("computer-", chassis);
279 return strdup("computer");
282 static int write_data_hostname(void) {
285 if (isempty(data[PROP_HOSTNAME]))
288 hn = data[PROP_HOSTNAME];
290 if (sethostname(hn, strlen(hn)) < 0)
296 static int write_data_static_hostname(void) {
298 if (isempty(data[PROP_STATIC_HOSTNAME])) {
300 if (unlink("/etc/hostname") < 0)
301 return errno == ENOENT ? 0 : -errno;
305 return write_one_line_file_atomic_label("/etc/hostname", data[PROP_STATIC_HOSTNAME]);
308 static int write_data_other(void) {
310 static const char * const name[_PROP_MAX] = {
311 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
312 [PROP_ICON_NAME] = "ICON_NAME",
313 [PROP_CHASSIS] = "CHASSIS"
319 r = load_env_file("/etc/machine-info", &l);
320 if (r < 0 && r != -ENOENT)
323 for (p = 2; p < _PROP_MAX; p++) {
328 if (isempty(data[p])) {
329 strv_env_unset(l, name[p]);
333 if (asprintf(&t, "%s=%s", name[p], strempty(data[p])) < 0) {
338 u = strv_env_set(l, t);
347 if (strv_isempty(l)) {
349 if (unlink("/etc/machine-info") < 0)
350 return errno == ENOENT ? 0 : -errno;
355 r = write_env_file_label("/etc/machine-info", l);
361 static int bus_hostname_append_icon_name(DBusMessageIter *i, const char *property, void *userdata) {
363 _cleanup_free_ char *n = NULL;
368 if (isempty(data[PROP_ICON_NAME]))
369 name = n = fallback_icon_name();
371 name = data[PROP_ICON_NAME];
373 return bus_property_append_string(i, property, (void*) name);
376 static int bus_hostname_append_chassis(DBusMessageIter *i, const char *property, void *userdata) {
382 if (isempty(data[PROP_CHASSIS]))
383 name = fallback_chassis();
385 name = data[PROP_CHASSIS];
387 return bus_property_append_string(i, property, (void*) name);
390 static const BusProperty bus_hostname_properties[] = {
391 { "Hostname", bus_property_append_string, "s", sizeof(data[0])*PROP_HOSTNAME, true },
392 { "StaticHostname", bus_property_append_string, "s", sizeof(data[0])*PROP_STATIC_HOSTNAME, true },
393 { "PrettyHostname", bus_property_append_string, "s", sizeof(data[0])*PROP_PRETTY_HOSTNAME, true },
394 { "IconName", bus_hostname_append_icon_name, "s", sizeof(data[0])*PROP_ICON_NAME, true },
395 { "Chassis", bus_hostname_append_chassis, "s", sizeof(data[0])*PROP_CHASSIS, true },
399 static const BusBoundProperties bps[] = {
400 { "org.freedesktop.hostname1", bus_hostname_properties, data },
404 static DBusHandlerResult hostname_message_handler(
405 DBusConnection *connection,
406 DBusMessage *message,
410 DBusMessage *reply = NULL, *changed = NULL;
417 dbus_error_init(&error);
419 if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetHostname")) {
421 dbus_bool_t interactive;
423 if (!dbus_message_get_args(
426 DBUS_TYPE_STRING, &name,
427 DBUS_TYPE_BOOLEAN, &interactive,
429 return bus_send_error_reply(connection, message, &error, -EINVAL);
432 name = data[PROP_STATIC_HOSTNAME];
437 if (!hostname_is_valid(name))
438 return bus_send_error_reply(connection, message, NULL, -EINVAL);
440 if (!streq_ptr(name, data[PROP_HOSTNAME])) {
443 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-hostname", interactive, NULL, &error);
445 return bus_send_error_reply(connection, message, &error, r);
451 free(data[PROP_HOSTNAME]);
452 data[PROP_HOSTNAME] = h;
454 r = write_data_hostname();
456 log_error("Failed to set host name: %s", strerror(-r));
457 return bus_send_error_reply(connection, message, NULL, r);
460 log_info("Changed host name to '%s'", strempty(data[PROP_HOSTNAME]));
462 changed = bus_properties_changed_new(
463 "/org/freedesktop/hostname1",
464 "org.freedesktop.hostname1",
470 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetStaticHostname")) {
472 dbus_bool_t interactive;
474 if (!dbus_message_get_args(
477 DBUS_TYPE_STRING, &name,
478 DBUS_TYPE_BOOLEAN, &interactive,
480 return bus_send_error_reply(connection, message, &error, -EINVAL);
485 if (!streq_ptr(name, data[PROP_STATIC_HOSTNAME])) {
487 r = verify_polkit(connection, message, "org.freedesktop.hostname1.set-static-hostname", interactive, NULL, &error);
489 return bus_send_error_reply(connection, message, &error, r);
492 free(data[PROP_STATIC_HOSTNAME]);
493 data[PROP_STATIC_HOSTNAME] = NULL;
497 if (!hostname_is_valid(name))
498 return bus_send_error_reply(connection, message, NULL, -EINVAL);
504 free(data[PROP_STATIC_HOSTNAME]);
505 data[PROP_STATIC_HOSTNAME] = h;
508 r = write_data_static_hostname();
510 log_error("Failed to write static host name: %s", strerror(-r));
511 return bus_send_error_reply(connection, message, NULL, r);
514 log_info("Changed static host name to '%s'", strempty(data[PROP_STATIC_HOSTNAME]));
516 changed = bus_properties_changed_new(
517 "/org/freedesktop/hostname1",
518 "org.freedesktop.hostname1",
524 } else if (dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetPrettyHostname") ||
525 dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetIconName") ||
526 dbus_message_is_method_call(message, "org.freedesktop.hostname1", "SetChassis")) {
529 dbus_bool_t interactive;
532 if (!dbus_message_get_args(
535 DBUS_TYPE_STRING, &name,
536 DBUS_TYPE_BOOLEAN, &interactive,
538 return bus_send_error_reply(connection, message, &error, -EINVAL);
543 k = streq(dbus_message_get_member(message), "SetPrettyHostname") ? PROP_PRETTY_HOSTNAME :
544 streq(dbus_message_get_member(message), "SetChassis") ? PROP_CHASSIS : PROP_ICON_NAME;
546 if (!streq_ptr(name, data[k])) {
548 /* Since the pretty hostname should always be
549 * changed at the same time as the static one,
550 * use the same policy action for both... */
552 r = verify_polkit(connection, message, k == PROP_PRETTY_HOSTNAME ?
553 "org.freedesktop.hostname1.set-static-hostname" :
554 "org.freedesktop.hostname1.set-machine-info", interactive, NULL, &error);
556 return bus_send_error_reply(connection, message, &error, r);
564 /* The icon name might ultimately be
565 * used as file name, so better be
567 if (k == PROP_ICON_NAME && !filename_is_safe(name))
568 return bus_send_error_reply(connection, message, NULL, -EINVAL);
569 if (k == PROP_PRETTY_HOSTNAME && !pretty_string_is_safe(name))
570 return bus_send_error_reply(connection, message, NULL, -EINVAL);
571 if (k == PROP_CHASSIS && !valid_chassis(name))
572 return bus_send_error_reply(connection, message, NULL, -EINVAL);
582 r = write_data_other();
584 log_error("Failed to write machine info: %s", strerror(-r));
585 return bus_send_error_reply(connection, message, NULL, r);
588 log_info("Changed %s to '%s'",
589 k == PROP_PRETTY_HOSTNAME ? "pretty host name" :
590 k == PROP_CHASSIS ? "chassis" : "icon name", strempty(data[k]));
592 changed = bus_properties_changed_new(
593 "/org/freedesktop/hostname1",
594 "org.freedesktop.hostname1",
595 k == PROP_PRETTY_HOSTNAME ? "PrettyHostname\0" :
596 k == PROP_CHASSIS ? "Chassis\0" : "IconName\0");
602 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
604 reply = dbus_message_new_method_return(message);
608 if (!dbus_connection_send(connection, reply, NULL))
611 dbus_message_unref(reply);
616 if (!dbus_connection_send(connection, changed, NULL))
619 dbus_message_unref(changed);
622 return DBUS_HANDLER_RESULT_HANDLED;
626 dbus_message_unref(reply);
629 dbus_message_unref(changed);
631 dbus_error_free(&error);
633 return DBUS_HANDLER_RESULT_NEED_MEMORY;
636 static int connect_bus(DBusConnection **_bus) {
637 static const DBusObjectPathVTable hostname_vtable = {
638 .message_function = hostname_message_handler
641 DBusConnection *bus = NULL;
646 dbus_error_init(&error);
648 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
650 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
655 dbus_connection_set_exit_on_disconnect(bus, FALSE);
657 if (!dbus_connection_register_object_path(bus, "/org/freedesktop/hostname1", &hostname_vtable, NULL) ||
658 !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
663 r = dbus_bus_request_name(bus, "org.freedesktop.hostname1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
664 if (dbus_error_is_set(&error)) {
665 log_error("Failed to register name on bus: %s", bus_error_message(&error));
670 if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
671 log_error("Failed to acquire name.");
682 dbus_connection_close(bus);
683 dbus_connection_unref(bus);
685 dbus_error_free(&error);
690 int main(int argc, char *argv[]) {
692 DBusConnection *bus = NULL;
693 bool exiting = false;
695 log_set_target(LOG_TARGET_AUTO);
696 log_parse_environment();
702 if (argc == 2 && streq(argv[1], "--introspect")) {
703 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
705 fputs(hostname_interface, stdout);
706 fputs("</node>\n", stdout);
711 log_error("This program takes no arguments.");
717 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
721 log_error("Failed to read hostname data: %s", strerror(-r));
725 r = connect_bus(&bus);
729 remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
732 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
735 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
737 bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1");
747 dbus_connection_flush(bus);
748 dbus_connection_close(bus);
749 dbus_connection_unref(bus);
752 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;