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/>.
26 #include <sys/utsname.h>
33 #include "fileio-label.h"
36 #include "event-util.h"
51 typedef struct Context {
52 char *data[_PROP_MAX];
53 Hashmap *polkit_registry;
56 static void context_reset(Context *c) {
61 for (p = 0; p < _PROP_MAX; p++) {
67 static void context_free(Context *c, sd_bus *bus) {
71 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
74 static int context_read_data(Context *c) {
82 assert_se(uname(&u) >= 0);
83 c->data[PROP_KERNEL_NAME] = strdup(u.sysname);
84 c->data[PROP_KERNEL_RELEASE] = strdup(u.release);
85 if (!c->data[PROP_KERNEL_NAME] || !c->data[PROP_KERNEL_RELEASE])
88 c->data[PROP_HOSTNAME] = gethostname_malloc();
89 if (!c->data[PROP_HOSTNAME])
92 r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
93 if (r < 0 && r != -ENOENT)
96 r = parse_env_file("/etc/machine-info", NEWLINE,
97 "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
98 "ICON_NAME", &c->data[PROP_ICON_NAME],
99 "CHASSIS", &c->data[PROP_CHASSIS],
101 if (r < 0 && r != -ENOENT)
104 r = parse_env_file("/etc/os-release", NEWLINE,
105 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
106 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
108 if (r < 0 && r != -ENOENT)
114 static bool check_nss(void) {
117 dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
126 static bool valid_chassis(const char *chassis) {
130 return nulstr_contains(
141 static const char* fallback_chassis(void) {
147 v = detect_virtualization(NULL);
149 if (v == VIRTUALIZATION_VM)
151 if (v == VIRTUALIZATION_CONTAINER)
154 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
158 r = safe_atou(type, &t);
163 /* We only list the really obvious cases here as the ACPI data
164 * is not really super reliable.
166 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
168 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
191 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
195 r = safe_atou(type, &t);
200 /* We only list the really obvious cases here. The DMI data is
201 unreliable enough, so let's not do any additional guesswork
204 See the SMBIOS Specification 2.7.1 section 7.4.1 for
205 details about the values listed here:
207 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
235 static char* context_fallback_icon_name(Context *c) {
240 if (!isempty(c->data[PROP_CHASSIS]))
241 return strappend("computer-", c->data[PROP_CHASSIS]);
243 chassis = fallback_chassis();
245 return strappend("computer-", chassis);
247 return strdup("computer");
250 static int context_write_data_hostname(Context *c) {
255 if (isempty(c->data[PROP_HOSTNAME]))
258 hn = c->data[PROP_HOSTNAME];
260 if (sethostname(hn, strlen(hn)) < 0)
266 static int context_write_data_static_hostname(Context *c) {
270 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
272 if (unlink("/etc/hostname") < 0)
273 return errno == ENOENT ? 0 : -errno;
277 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
280 static int context_write_data_machine_info(Context *c) {
282 static const char * const name[_PROP_MAX] = {
283 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
284 [PROP_ICON_NAME] = "ICON_NAME",
285 [PROP_CHASSIS] = "CHASSIS"
288 _cleanup_strv_free_ char **l = NULL;
293 r = load_env_file("/etc/machine-info", NULL, &l);
294 if (r < 0 && r != -ENOENT)
297 for (p = PROP_PRETTY_HOSTNAME; p <= PROP_CHASSIS; p++) {
302 if (isempty(c->data[p])) {
303 strv_env_unset(l, name[p]);
307 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0)
310 u = strv_env_set(l, t);
320 if (strv_isempty(l)) {
322 if (unlink("/etc/machine-info") < 0)
323 return errno == ENOENT ? 0 : -errno;
328 return write_env_file_label("/etc/machine-info", l);
331 static int property_get_icon_name(
334 const char *interface,
335 const char *property,
336 sd_bus_message *reply,
338 sd_bus_error *error) {
340 _cleanup_free_ char *n = NULL;
341 Context *c = userdata;
344 if (isempty(c->data[PROP_ICON_NAME]))
345 name = n = context_fallback_icon_name(c);
347 name = c->data[PROP_ICON_NAME];
352 return sd_bus_message_append(reply, "s", name);
355 static int property_get_chassis(
358 const char *interface,
359 const char *property,
360 sd_bus_message *reply,
362 sd_bus_error *error) {
364 Context *c = userdata;
367 if (isempty(c->data[PROP_CHASSIS]))
368 name = fallback_chassis();
370 name = c->data[PROP_CHASSIS];
372 return sd_bus_message_append(reply, "s", name);
375 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
376 Context *c = userdata;
382 r = sd_bus_message_read(m, "sb", &name, &interactive);
387 name = c->data[PROP_STATIC_HOSTNAME];
392 if (!hostname_is_valid(name))
393 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
395 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
396 return sd_bus_reply_method_return(m, NULL);
398 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
402 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
408 free(c->data[PROP_HOSTNAME]);
409 c->data[PROP_HOSTNAME] = h;
411 r = context_write_data_hostname(c);
413 log_error("Failed to set host name: %s", strerror(-r));
414 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
417 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
419 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
421 return sd_bus_reply_method_return(m, NULL);
424 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
425 Context *c = userdata;
430 r = sd_bus_message_read(m, "sb", &name, &interactive);
437 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
438 return sd_bus_reply_method_return(m, NULL);
440 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
444 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
447 free(c->data[PROP_STATIC_HOSTNAME]);
448 c->data[PROP_STATIC_HOSTNAME] = NULL;
452 if (!hostname_is_valid(name))
453 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
459 free(c->data[PROP_STATIC_HOSTNAME]);
460 c->data[PROP_STATIC_HOSTNAME] = h;
463 r = context_write_data_static_hostname(c);
465 log_error("Failed to write static host name: %s", strerror(-r));
466 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
469 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
471 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
473 return sd_bus_reply_method_return(m, NULL);
476 static int set_machine_info(Context *c, sd_bus *bus, sd_bus_message *m, int prop, sd_bus_message_handler_t cb, sd_bus_error *error) {
485 r = sd_bus_message_read(m, "sb", &name, &interactive);
492 if (streq_ptr(name, c->data[prop]))
493 return sd_bus_reply_method_return(m, NULL);
495 /* Since the pretty hostname should always be changed at the
496 * same time as the static one, use the same policy action for
499 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
500 "org.freedesktop.hostname1.set-static-hostname" :
501 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
505 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
509 c->data[prop] = NULL;
513 /* The icon name might ultimately be used as file
514 * name, so better be safe than sorry */
516 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
517 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
518 if (prop == PROP_PRETTY_HOSTNAME &&
519 (string_has_cc(name) || chars_intersect(name, "\t")))
520 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
521 if (prop == PROP_CHASSIS && !valid_chassis(name))
522 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
532 r = context_write_data_machine_info(c);
534 log_error("Failed to write machine info: %s", strerror(-r));
535 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
538 log_info("Changed %s to '%s'",
539 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
540 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
542 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
543 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
544 prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
546 return sd_bus_reply_method_return(m, NULL);
549 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
550 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
553 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
554 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
557 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
558 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
561 static const sd_bus_vtable hostname_vtable[] = {
562 SD_BUS_VTABLE_START(0),
563 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
564 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
565 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
566 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
567 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
568 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
569 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
570 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
571 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
572 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
573 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
574 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
575 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
576 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
580 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
581 _cleanup_bus_unref_ sd_bus *bus = NULL;
588 r = sd_bus_default_system(&bus);
590 log_error("Failed to get system bus connection: %s", strerror(-r));
594 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
596 log_error("Failed to register object: %s", strerror(-r));
600 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
602 log_error("Failed to register name: %s", strerror(-r));
606 r = sd_bus_attach_event(bus, event, 0);
608 log_error("Failed to attach bus to event loop: %s", strerror(-r));
618 int main(int argc, char *argv[]) {
619 Context context = {};
621 _cleanup_event_unref_ sd_event *event = NULL;
622 _cleanup_bus_unref_ sd_bus *bus = NULL;
625 log_set_target(LOG_TARGET_AUTO);
626 log_parse_environment();
633 log_error("This program takes no arguments.");
639 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
642 log_error("This program takes no arguments.");
647 r = sd_event_default(&event);
649 log_error("Failed to allocate event loop: %s", strerror(-r));
653 sd_event_set_watchdog(event, true);
655 r = connect_bus(&context, event, &bus);
659 r = context_read_data(&context);
661 log_error("Failed to read hostname and machine information: %s", strerror(-r));
665 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
667 log_error("Failed to run event loop: %s", strerror(-r));
672 context_free(&context, bus);
674 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;