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],
112 r = parse_env_file("/usr/lib/os-release", NEWLINE,
113 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
114 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
118 if (r < 0 && r != -ENOENT)
124 static bool check_nss(void) {
127 dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
136 static bool valid_chassis(const char *chassis) {
140 return nulstr_contains(
151 static const char* fallback_chassis(void) {
157 v = detect_virtualization(NULL);
159 if (v == VIRTUALIZATION_VM)
161 if (v == VIRTUALIZATION_CONTAINER)
164 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
168 r = safe_atou(type, &t);
173 /* We only list the really obvious cases here as the ACPI data
174 * is not really super reliable.
176 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
178 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
201 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
205 r = safe_atou(type, &t);
210 /* We only list the really obvious cases here. The DMI data is
211 unreliable enough, so let's not do any additional guesswork
214 See the SMBIOS Specification 2.7.1 section 7.4.1 for
215 details about the values listed here:
217 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
245 static char* context_fallback_icon_name(Context *c) {
250 if (!isempty(c->data[PROP_CHASSIS]))
251 return strappend("computer-", c->data[PROP_CHASSIS]);
253 chassis = fallback_chassis();
255 return strappend("computer-", chassis);
257 return strdup("computer");
260 static bool hostname_is_useful(const char *hn) {
261 return !isempty(hn) && !streq(hn, "localhost");
264 static int context_update_kernel_hostname(Context *c) {
265 const char *static_hn;
270 static_hn = c->data[PROP_STATIC_HOSTNAME];
272 /* /etc/hostname with something other than "localhost"
273 * has the highest preference ... */
274 if (hostname_is_useful(static_hn))
277 /* ... the transient host name, (ie: DHCP) comes next ...*/
278 else if (!isempty(c->data[PROP_HOSTNAME]))
279 hn = c->data[PROP_HOSTNAME];
281 /* ... fallback to static "localhost.*" ignored above ... */
282 else if (!isempty(static_hn))
285 /* ... and the ultimate fallback */
289 if (sethostname(hn, strlen(hn)) < 0)
295 static int context_write_data_static_hostname(Context *c) {
299 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
301 if (unlink("/etc/hostname") < 0)
302 return errno == ENOENT ? 0 : -errno;
306 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
309 static int context_write_data_machine_info(Context *c) {
311 static const char * const name[_PROP_MAX] = {
312 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
313 [PROP_ICON_NAME] = "ICON_NAME",
314 [PROP_CHASSIS] = "CHASSIS"
317 _cleanup_strv_free_ char **l = NULL;
322 r = load_env_file("/etc/machine-info", NULL, &l);
323 if (r < 0 && r != -ENOENT)
326 for (p = PROP_PRETTY_HOSTNAME; p <= PROP_CHASSIS; p++) {
331 if (isempty(c->data[p])) {
332 strv_env_unset(l, name[p]);
336 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0)
339 u = strv_env_set(l, t);
349 if (strv_isempty(l)) {
351 if (unlink("/etc/machine-info") < 0)
352 return errno == ENOENT ? 0 : -errno;
357 return write_env_file_label("/etc/machine-info", l);
360 static int property_get_icon_name(
363 const char *interface,
364 const char *property,
365 sd_bus_message *reply,
367 sd_bus_error *error) {
369 _cleanup_free_ char *n = NULL;
370 Context *c = userdata;
373 if (isempty(c->data[PROP_ICON_NAME]))
374 name = n = context_fallback_icon_name(c);
376 name = c->data[PROP_ICON_NAME];
381 return sd_bus_message_append(reply, "s", name);
384 static int property_get_chassis(
387 const char *interface,
388 const char *property,
389 sd_bus_message *reply,
391 sd_bus_error *error) {
393 Context *c = userdata;
396 if (isempty(c->data[PROP_CHASSIS]))
397 name = fallback_chassis();
399 name = c->data[PROP_CHASSIS];
401 return sd_bus_message_append(reply, "s", name);
404 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
405 Context *c = userdata;
411 r = sd_bus_message_read(m, "sb", &name, &interactive);
416 name = c->data[PROP_STATIC_HOSTNAME];
421 if (!hostname_is_valid(name))
422 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
424 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
425 return sd_bus_reply_method_return(m, NULL);
427 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
431 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
437 free(c->data[PROP_HOSTNAME]);
438 c->data[PROP_HOSTNAME] = h;
440 r = context_update_kernel_hostname(c);
442 log_error("Failed to set host name: %s", strerror(-r));
443 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
446 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
448 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
450 return sd_bus_reply_method_return(m, NULL);
453 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
454 Context *c = userdata;
459 r = sd_bus_message_read(m, "sb", &name, &interactive);
466 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
467 return sd_bus_reply_method_return(m, NULL);
469 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
473 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
476 free(c->data[PROP_STATIC_HOSTNAME]);
477 c->data[PROP_STATIC_HOSTNAME] = NULL;
481 if (!hostname_is_valid(name))
482 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
488 free(c->data[PROP_STATIC_HOSTNAME]);
489 c->data[PROP_STATIC_HOSTNAME] = h;
492 r = context_update_kernel_hostname(c);
494 log_error("Failed to set host name: %s", strerror(-r));
495 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
498 r = context_write_data_static_hostname(c);
500 log_error("Failed to write static host name: %s", strerror(-r));
501 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
504 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
506 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
508 return sd_bus_reply_method_return(m, NULL);
511 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) {
520 r = sd_bus_message_read(m, "sb", &name, &interactive);
527 if (streq_ptr(name, c->data[prop]))
528 return sd_bus_reply_method_return(m, NULL);
530 /* Since the pretty hostname should always be changed at the
531 * same time as the static one, use the same policy action for
534 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
535 "org.freedesktop.hostname1.set-static-hostname" :
536 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
540 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
544 c->data[prop] = NULL;
548 /* The icon name might ultimately be used as file
549 * name, so better be safe than sorry */
551 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
552 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
553 if (prop == PROP_PRETTY_HOSTNAME &&
554 (string_has_cc(name) || chars_intersect(name, "\t")))
555 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
556 if (prop == PROP_CHASSIS && !valid_chassis(name))
557 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
567 r = context_write_data_machine_info(c);
569 log_error("Failed to write machine info: %s", strerror(-r));
570 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
573 log_info("Changed %s to '%s'",
574 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
575 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
577 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
578 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
579 prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
581 return sd_bus_reply_method_return(m, NULL);
584 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
585 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
588 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
589 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
592 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
593 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
596 static const sd_bus_vtable hostname_vtable[] = {
597 SD_BUS_VTABLE_START(0),
598 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
599 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
600 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
601 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
602 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
603 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
604 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
605 SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
606 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
607 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
608 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
609 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
610 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
611 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
612 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
616 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
617 _cleanup_bus_unref_ sd_bus *bus = NULL;
624 r = sd_bus_default_system(&bus);
626 log_error("Failed to get system bus connection: %s", strerror(-r));
630 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
632 log_error("Failed to register object: %s", strerror(-r));
636 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
638 log_error("Failed to register name: %s", strerror(-r));
642 r = sd_bus_attach_event(bus, event, 0);
644 log_error("Failed to attach bus to event loop: %s", strerror(-r));
654 int main(int argc, char *argv[]) {
655 Context context = {};
657 _cleanup_event_unref_ sd_event *event = NULL;
658 _cleanup_bus_unref_ sd_bus *bus = NULL;
661 log_set_target(LOG_TARGET_AUTO);
662 log_parse_environment();
669 log_error("This program takes no arguments.");
675 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
678 log_error("This program takes no arguments.");
683 r = sd_event_default(&event);
685 log_error("Failed to allocate event loop: %s", strerror(-r));
689 sd_event_set_watchdog(event, true);
691 r = connect_bus(&context, event, &bus);
695 r = context_read_data(&context);
697 log_error("Failed to read hostname and machine information: %s", strerror(-r));
701 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
703 log_error("Failed to run event loop: %s", strerror(-r));
708 context_free(&context, bus);
710 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;