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"
52 typedef struct Context {
53 char *data[_PROP_MAX];
54 Hashmap *polkit_registry;
57 static void context_reset(Context *c) {
62 for (p = 0; p < _PROP_MAX; p++) {
68 static void context_free(Context *c, sd_bus *bus) {
72 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
75 static int context_read_data(Context *c) {
83 assert_se(uname(&u) >= 0);
84 c->data[PROP_KERNEL_NAME] = strdup(u.sysname);
85 c->data[PROP_KERNEL_RELEASE] = strdup(u.release);
86 c->data[PROP_KERNEL_VERSION] = strdup(u.version);
87 if (!c->data[PROP_KERNEL_NAME] || !c->data[PROP_KERNEL_RELEASE] ||
88 !c->data[PROP_KERNEL_VERSION])
91 c->data[PROP_HOSTNAME] = gethostname_malloc();
92 if (!c->data[PROP_HOSTNAME])
95 r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
96 if (r < 0 && r != -ENOENT)
99 r = parse_env_file("/etc/machine-info", NEWLINE,
100 "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
101 "ICON_NAME", &c->data[PROP_ICON_NAME],
102 "CHASSIS", &c->data[PROP_CHASSIS],
104 if (r < 0 && r != -ENOENT)
107 r = parse_env_file("/etc/os-release", NEWLINE,
108 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
109 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
111 if (r < 0 && r != -ENOENT)
117 static bool check_nss(void) {
120 dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
129 static bool valid_chassis(const char *chassis) {
133 return nulstr_contains(
144 static const char* fallback_chassis(void) {
150 v = detect_virtualization(NULL);
152 if (v == VIRTUALIZATION_VM)
154 if (v == VIRTUALIZATION_CONTAINER)
157 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
161 r = safe_atou(type, &t);
166 /* We only list the really obvious cases here as the ACPI data
167 * is not really super reliable.
169 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
171 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
194 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
198 r = safe_atou(type, &t);
203 /* We only list the really obvious cases here. The DMI data is
204 unreliable enough, so let's not do any additional guesswork
207 See the SMBIOS Specification 2.7.1 section 7.4.1 for
208 details about the values listed here:
210 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
238 static char* context_fallback_icon_name(Context *c) {
243 if (!isempty(c->data[PROP_CHASSIS]))
244 return strappend("computer-", c->data[PROP_CHASSIS]);
246 chassis = fallback_chassis();
248 return strappend("computer-", chassis);
250 return strdup("computer");
253 static bool hostname_is_useful(const char *hn) {
254 return !isempty(hn) && !streq(hn, "localhost");
257 static int context_update_kernel_hostname(Context *c) {
258 const char *static_hn;
263 static_hn = c->data[PROP_STATIC_HOSTNAME];
265 /* /etc/hostname with something other than "localhost"
266 * has the highest preference ... */
267 if (hostname_is_useful(static_hn))
270 /* ... the transient host name, (ie: DHCP) comes next ...*/
271 else if (!isempty(c->data[PROP_HOSTNAME]))
272 hn = c->data[PROP_HOSTNAME];
274 /* ... fallback to static "localhost.*" ignored above ... */
275 else if (!isempty(static_hn))
278 /* ... and the ultimate fallback */
282 if (sethostname(hn, strlen(hn)) < 0)
288 static int context_write_data_static_hostname(Context *c) {
292 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
294 if (unlink("/etc/hostname") < 0)
295 return errno == ENOENT ? 0 : -errno;
299 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
302 static int context_write_data_machine_info(Context *c) {
304 static const char * const name[_PROP_MAX] = {
305 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
306 [PROP_ICON_NAME] = "ICON_NAME",
307 [PROP_CHASSIS] = "CHASSIS"
310 _cleanup_strv_free_ char **l = NULL;
315 r = load_env_file("/etc/machine-info", NULL, &l);
316 if (r < 0 && r != -ENOENT)
319 for (p = PROP_PRETTY_HOSTNAME; p <= PROP_CHASSIS; p++) {
324 if (isempty(c->data[p])) {
325 strv_env_unset(l, name[p]);
329 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0)
332 u = strv_env_set(l, t);
342 if (strv_isempty(l)) {
344 if (unlink("/etc/machine-info") < 0)
345 return errno == ENOENT ? 0 : -errno;
350 return write_env_file_label("/etc/machine-info", l);
353 static int property_get_icon_name(
356 const char *interface,
357 const char *property,
358 sd_bus_message *reply,
360 sd_bus_error *error) {
362 _cleanup_free_ char *n = NULL;
363 Context *c = userdata;
366 if (isempty(c->data[PROP_ICON_NAME]))
367 name = n = context_fallback_icon_name(c);
369 name = c->data[PROP_ICON_NAME];
374 return sd_bus_message_append(reply, "s", name);
377 static int property_get_chassis(
380 const char *interface,
381 const char *property,
382 sd_bus_message *reply,
384 sd_bus_error *error) {
386 Context *c = userdata;
389 if (isempty(c->data[PROP_CHASSIS]))
390 name = fallback_chassis();
392 name = c->data[PROP_CHASSIS];
394 return sd_bus_message_append(reply, "s", name);
397 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
398 Context *c = userdata;
404 r = sd_bus_message_read(m, "sb", &name, &interactive);
409 name = c->data[PROP_STATIC_HOSTNAME];
414 if (!hostname_is_valid(name))
415 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
417 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
418 return sd_bus_reply_method_return(m, NULL);
420 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
424 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
430 free(c->data[PROP_HOSTNAME]);
431 c->data[PROP_HOSTNAME] = h;
433 r = context_update_kernel_hostname(c);
435 log_error("Failed to set host name: %s", strerror(-r));
436 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
439 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
441 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
443 return sd_bus_reply_method_return(m, NULL);
446 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
447 Context *c = userdata;
452 r = sd_bus_message_read(m, "sb", &name, &interactive);
459 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
460 return sd_bus_reply_method_return(m, NULL);
462 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
466 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
469 free(c->data[PROP_STATIC_HOSTNAME]);
470 c->data[PROP_STATIC_HOSTNAME] = NULL;
474 if (!hostname_is_valid(name))
475 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
481 free(c->data[PROP_STATIC_HOSTNAME]);
482 c->data[PROP_STATIC_HOSTNAME] = h;
485 r = context_update_kernel_hostname(c);
487 log_error("Failed to set host name: %s", strerror(-r));
488 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
491 r = context_write_data_static_hostname(c);
493 log_error("Failed to write static host name: %s", strerror(-r));
494 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
497 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
499 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
501 return sd_bus_reply_method_return(m, NULL);
504 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) {
513 r = sd_bus_message_read(m, "sb", &name, &interactive);
520 if (streq_ptr(name, c->data[prop]))
521 return sd_bus_reply_method_return(m, NULL);
523 /* Since the pretty hostname should always be changed at the
524 * same time as the static one, use the same policy action for
527 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
528 "org.freedesktop.hostname1.set-static-hostname" :
529 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
533 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
537 c->data[prop] = NULL;
541 /* The icon name might ultimately be used as file
542 * name, so better be safe than sorry */
544 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
545 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
546 if (prop == PROP_PRETTY_HOSTNAME &&
547 (string_has_cc(name) || chars_intersect(name, "\t")))
548 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
549 if (prop == PROP_CHASSIS && !valid_chassis(name))
550 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
560 r = context_write_data_machine_info(c);
562 log_error("Failed to write machine info: %s", strerror(-r));
563 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
566 log_info("Changed %s to '%s'",
567 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
568 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
570 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
571 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
572 prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
574 return sd_bus_reply_method_return(m, NULL);
577 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
578 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
581 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
582 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
585 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
586 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
589 static const sd_bus_vtable hostname_vtable[] = {
590 SD_BUS_VTABLE_START(0),
591 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
592 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
593 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
594 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
595 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
596 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
597 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
598 SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
599 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
600 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
601 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
602 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
603 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
604 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
605 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
609 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
610 _cleanup_bus_unref_ sd_bus *bus = NULL;
617 r = sd_bus_default_system(&bus);
619 log_error("Failed to get system bus connection: %s", strerror(-r));
623 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
625 log_error("Failed to register object: %s", strerror(-r));
629 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
631 log_error("Failed to register name: %s", strerror(-r));
635 r = sd_bus_attach_event(bus, event, 0);
637 log_error("Failed to attach bus to event loop: %s", strerror(-r));
647 int main(int argc, char *argv[]) {
648 Context context = {};
650 _cleanup_event_unref_ sd_event *event = NULL;
651 _cleanup_bus_unref_ sd_bus *bus = NULL;
654 log_set_target(LOG_TARGET_AUTO);
655 log_parse_environment();
662 log_error("This program takes no arguments.");
668 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
671 log_error("This program takes no arguments.");
676 r = sd_event_default(&event);
678 log_error("Failed to allocate event loop: %s", strerror(-r));
682 sd_event_set_watchdog(event, true);
684 r = connect_bus(&context, event, &bus);
688 r = context_read_data(&context);
690 log_error("Failed to read hostname and machine information: %s", strerror(-r));
694 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
696 log_error("Failed to run event loop: %s", strerror(-r));
701 context_free(&context, bus);
703 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;