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"
38 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
55 typedef struct Context {
56 char *data[_PROP_MAX];
57 Hashmap *polkit_registry;
60 static void context_reset(Context *c) {
65 for (p = 0; p < _PROP_MAX; p++) {
71 static void context_free(Context *c, sd_bus *bus) {
75 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
78 static int context_read_data(Context *c) {
86 assert_se(uname(&u) >= 0);
87 c->data[PROP_KERNEL_NAME] = strdup(u.sysname);
88 c->data[PROP_KERNEL_RELEASE] = strdup(u.release);
89 c->data[PROP_KERNEL_VERSION] = strdup(u.version);
90 if (!c->data[PROP_KERNEL_NAME] || !c->data[PROP_KERNEL_RELEASE] ||
91 !c->data[PROP_KERNEL_VERSION])
94 c->data[PROP_HOSTNAME] = gethostname_malloc();
95 if (!c->data[PROP_HOSTNAME])
98 r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
99 if (r < 0 && r != -ENOENT)
102 r = parse_env_file("/etc/machine-info", NEWLINE,
103 "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
104 "ICON_NAME", &c->data[PROP_ICON_NAME],
105 "CHASSIS", &c->data[PROP_CHASSIS],
106 "DEPLOYMENT", &c->data[PROP_DEPLOYMENT],
108 if (r < 0 && r != -ENOENT)
111 r = parse_env_file("/etc/os-release", NEWLINE,
112 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
113 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
116 r = parse_env_file("/usr/lib/os-release", NEWLINE,
117 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
118 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
122 if (r < 0 && r != -ENOENT)
128 static bool check_nss(void) {
131 dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
140 static bool valid_chassis(const char *chassis) {
143 return nulstr_contains(
155 static bool valid_deployment(const char *deployment) {
158 return strspn(deployment, VALID_DEPLOYMENT_CHARS) == strlen(deployment);
161 static const char* fallback_chassis(void) {
167 v = detect_virtualization(NULL);
169 if (v == VIRTUALIZATION_VM)
171 if (v == VIRTUALIZATION_CONTAINER)
174 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
178 r = safe_atou(type, &t);
183 /* We only list the really obvious cases here as the ACPI data
184 * is not really super reliable.
186 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
188 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
211 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
215 r = safe_atou(type, &t);
220 /* We only list the really obvious cases here. The DMI data is
221 unreliable enough, so let's not do any additional guesswork
224 See the SMBIOS Specification 2.7.1 section 7.4.1 for
225 details about the values listed here:
227 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
255 static char* context_fallback_icon_name(Context *c) {
260 if (!isempty(c->data[PROP_CHASSIS]))
261 return strappend("computer-", c->data[PROP_CHASSIS]);
263 chassis = fallback_chassis();
265 return strappend("computer-", chassis);
267 return strdup("computer");
271 static bool hostname_is_useful(const char *hn) {
272 return !isempty(hn) && !is_localhost(hn);
275 static int context_update_kernel_hostname(Context *c) {
276 const char *static_hn;
281 static_hn = c->data[PROP_STATIC_HOSTNAME];
283 /* /etc/hostname with something other than "localhost"
284 * has the highest preference ... */
285 if (hostname_is_useful(static_hn))
288 /* ... the transient host name, (ie: DHCP) comes next ...*/
289 else if (!isempty(c->data[PROP_HOSTNAME]))
290 hn = c->data[PROP_HOSTNAME];
292 /* ... fallback to static "localhost.*" ignored above ... */
293 else if (!isempty(static_hn))
296 /* ... and the ultimate fallback */
300 if (sethostname(hn, strlen(hn)) < 0)
306 static int context_write_data_static_hostname(Context *c) {
310 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
312 if (unlink("/etc/hostname") < 0)
313 return errno == ENOENT ? 0 : -errno;
317 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
320 static int context_write_data_machine_info(Context *c) {
322 static const char * const name[_PROP_MAX] = {
323 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
324 [PROP_ICON_NAME] = "ICON_NAME",
325 [PROP_CHASSIS] = "CHASSIS",
326 [PROP_DEPLOYMENT] = "DEPLOYMENT",
329 _cleanup_strv_free_ char **l = NULL;
334 r = load_env_file(NULL, "/etc/machine-info", NULL, &l);
335 if (r < 0 && r != -ENOENT)
338 for (p = PROP_PRETTY_HOSTNAME; p <= PROP_DEPLOYMENT; p++) {
339 _cleanup_free_ char *t = NULL;
344 if (isempty(c->data[p])) {
345 strv_env_unset(l, name[p]);
349 t = strjoin(name[p], "=", c->data[p], NULL);
353 u = strv_env_set(l, t);
361 if (strv_isempty(l)) {
362 if (unlink("/etc/machine-info") < 0)
363 return errno == ENOENT ? 0 : -errno;
368 return write_env_file_label("/etc/machine-info", l);
371 static int property_get_icon_name(
374 const char *interface,
375 const char *property,
376 sd_bus_message *reply,
378 sd_bus_error *error) {
380 _cleanup_free_ char *n = NULL;
381 Context *c = userdata;
384 if (isempty(c->data[PROP_ICON_NAME]))
385 name = n = context_fallback_icon_name(c);
387 name = c->data[PROP_ICON_NAME];
392 return sd_bus_message_append(reply, "s", name);
395 static int property_get_chassis(
398 const char *interface,
399 const char *property,
400 sd_bus_message *reply,
402 sd_bus_error *error) {
404 Context *c = userdata;
407 if (isempty(c->data[PROP_CHASSIS]))
408 name = fallback_chassis();
410 name = c->data[PROP_CHASSIS];
412 return sd_bus_message_append(reply, "s", name);
415 static int property_get_deployment(
418 const char *interface,
419 const char *property,
420 sd_bus_message *reply,
422 sd_bus_error *error) {
424 Context *c = userdata;
427 name = c->data[PROP_DEPLOYMENT];
429 return sd_bus_message_append(reply, "s", name);
432 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
433 Context *c = userdata;
439 r = sd_bus_message_read(m, "sb", &name, &interactive);
444 name = c->data[PROP_STATIC_HOSTNAME];
449 if (!hostname_is_valid(name))
450 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
452 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
453 return sd_bus_reply_method_return(m, NULL);
455 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
459 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
465 free(c->data[PROP_HOSTNAME]);
466 c->data[PROP_HOSTNAME] = h;
468 r = context_update_kernel_hostname(c);
470 log_error("Failed to set host name: %s", strerror(-r));
471 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
474 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
476 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
478 return sd_bus_reply_method_return(m, NULL);
481 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
482 Context *c = userdata;
487 r = sd_bus_message_read(m, "sb", &name, &interactive);
494 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
495 return sd_bus_reply_method_return(m, NULL);
497 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
501 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
504 free(c->data[PROP_STATIC_HOSTNAME]);
505 c->data[PROP_STATIC_HOSTNAME] = NULL;
509 if (!hostname_is_valid(name))
510 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
516 free(c->data[PROP_STATIC_HOSTNAME]);
517 c->data[PROP_STATIC_HOSTNAME] = h;
520 r = context_update_kernel_hostname(c);
522 log_error("Failed to set host name: %s", strerror(-r));
523 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
526 r = context_write_data_static_hostname(c);
528 log_error("Failed to write static host name: %s", strerror(-r));
529 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
532 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
534 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
536 return sd_bus_reply_method_return(m, NULL);
539 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) {
548 r = sd_bus_message_read(m, "sb", &name, &interactive);
555 if (streq_ptr(name, c->data[prop]))
556 return sd_bus_reply_method_return(m, NULL);
558 /* Since the pretty hostname should always be changed at the
559 * same time as the static one, use the same policy action for
562 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
563 "org.freedesktop.hostname1.set-static-hostname" :
564 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
568 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
572 c->data[prop] = NULL;
576 /* The icon name might ultimately be used as file
577 * name, so better be safe than sorry */
579 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
580 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
581 if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL))
582 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
583 if (prop == PROP_CHASSIS && !valid_chassis(name))
584 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
585 if (prop == PROP_DEPLOYMENT && !valid_deployment(name))
586 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name);
596 r = context_write_data_machine_info(c);
598 log_error("Failed to write machine info: %s", strerror(-r));
599 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
602 log_info("Changed %s to '%s'",
603 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
604 prop == PROP_DEPLOYMENT ? "deployment" :
605 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
607 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
608 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
609 prop == PROP_DEPLOYMENT ? "Deployment" :
610 prop == PROP_CHASSIS ? "Chassis" : "IconName" , NULL);
612 return sd_bus_reply_method_return(m, NULL);
615 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
616 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
619 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
620 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
623 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
624 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
627 static int method_set_deployment(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
628 return set_machine_info(userdata, bus, m, PROP_DEPLOYMENT, method_set_deployment, error);
631 static const sd_bus_vtable hostname_vtable[] = {
632 SD_BUS_VTABLE_START(0),
633 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
634 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
635 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
636 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
637 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
638 SD_BUS_PROPERTY("Deployment", "s", property_get_deployment, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
639 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
640 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
641 SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
642 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
643 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
644 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
645 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
646 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
647 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
648 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
649 SD_BUS_METHOD("SetDeployment", "sb", NULL, method_set_deployment, SD_BUS_VTABLE_UNPRIVILEGED),
653 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
654 _cleanup_bus_unref_ sd_bus *bus = NULL;
661 r = sd_bus_default_system(&bus);
663 log_error("Failed to get system bus connection: %s", strerror(-r));
667 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
669 log_error("Failed to register object: %s", strerror(-r));
673 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
675 log_error("Failed to register name: %s", strerror(-r));
679 r = sd_bus_attach_event(bus, event, 0);
681 log_error("Failed to attach bus to event loop: %s", strerror(-r));
691 int main(int argc, char *argv[]) {
692 Context context = {};
694 _cleanup_event_unref_ sd_event *event = NULL;
695 _cleanup_bus_unref_ sd_bus *bus = NULL;
698 log_set_target(LOG_TARGET_AUTO);
699 log_parse_environment();
706 log_error("This program takes no arguments.");
712 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
715 log_error("This program takes no arguments.");
720 r = sd_event_default(&event);
722 log_error("Failed to allocate event loop: %s", strerror(-r));
726 sd_event_set_watchdog(event, true);
728 r = connect_bus(&context, event, &bus);
732 r = context_read_data(&context);
734 log_error("Failed to read hostname and machine information: %s", strerror(-r));
738 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
740 log_error("Failed to run event loop: %s", strerror(-r));
745 context_free(&context, bus);
747 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;