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++) {
343 if (isempty(c->data[p])) {
344 strv_env_unset(l, name[p]);
348 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0)
351 u = strv_env_set(l, t);
361 if (strv_isempty(l)) {
363 if (unlink("/etc/machine-info") < 0)
364 return errno == ENOENT ? 0 : -errno;
369 return write_env_file_label("/etc/machine-info", l);
372 static int property_get_icon_name(
375 const char *interface,
376 const char *property,
377 sd_bus_message *reply,
379 sd_bus_error *error) {
381 _cleanup_free_ char *n = NULL;
382 Context *c = userdata;
385 if (isempty(c->data[PROP_ICON_NAME]))
386 name = n = context_fallback_icon_name(c);
388 name = c->data[PROP_ICON_NAME];
393 return sd_bus_message_append(reply, "s", name);
396 static int property_get_chassis(
399 const char *interface,
400 const char *property,
401 sd_bus_message *reply,
403 sd_bus_error *error) {
405 Context *c = userdata;
408 if (isempty(c->data[PROP_CHASSIS]))
409 name = fallback_chassis();
411 name = c->data[PROP_CHASSIS];
413 return sd_bus_message_append(reply, "s", name);
416 static int property_get_deployment(
419 const char *interface,
420 const char *property,
421 sd_bus_message *reply,
423 sd_bus_error *error) {
425 Context *c = userdata;
428 name = c->data[PROP_DEPLOYMENT];
430 return sd_bus_message_append(reply, "s", name);
433 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
434 Context *c = userdata;
440 r = sd_bus_message_read(m, "sb", &name, &interactive);
445 name = c->data[PROP_STATIC_HOSTNAME];
450 if (!hostname_is_valid(name))
451 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
453 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
454 return sd_bus_reply_method_return(m, NULL);
456 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
460 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
466 free(c->data[PROP_HOSTNAME]);
467 c->data[PROP_HOSTNAME] = h;
469 r = context_update_kernel_hostname(c);
471 log_error("Failed to set host name: %s", strerror(-r));
472 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
475 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
477 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
479 return sd_bus_reply_method_return(m, NULL);
482 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
483 Context *c = userdata;
488 r = sd_bus_message_read(m, "sb", &name, &interactive);
495 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
496 return sd_bus_reply_method_return(m, NULL);
498 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
502 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
505 free(c->data[PROP_STATIC_HOSTNAME]);
506 c->data[PROP_STATIC_HOSTNAME] = NULL;
510 if (!hostname_is_valid(name))
511 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
517 free(c->data[PROP_STATIC_HOSTNAME]);
518 c->data[PROP_STATIC_HOSTNAME] = h;
521 r = context_update_kernel_hostname(c);
523 log_error("Failed to set host name: %s", strerror(-r));
524 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
527 r = context_write_data_static_hostname(c);
529 log_error("Failed to write static host name: %s", strerror(-r));
530 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
533 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
535 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
537 return sd_bus_reply_method_return(m, NULL);
540 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) {
549 r = sd_bus_message_read(m, "sb", &name, &interactive);
556 if (streq_ptr(name, c->data[prop]))
557 return sd_bus_reply_method_return(m, NULL);
559 /* Since the pretty hostname should always be changed at the
560 * same time as the static one, use the same policy action for
563 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
564 "org.freedesktop.hostname1.set-static-hostname" :
565 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
569 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
573 c->data[prop] = NULL;
577 /* The icon name might ultimately be used as file
578 * name, so better be safe than sorry */
580 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
581 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
582 if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL))
583 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
584 if (prop == PROP_CHASSIS && !valid_chassis(name))
585 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
586 if (prop == PROP_DEPLOYMENT && !valid_deployment(name))
587 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name);
597 r = context_write_data_machine_info(c);
599 log_error("Failed to write machine info: %s", strerror(-r));
600 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
603 log_info("Changed %s to '%s'",
604 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
605 prop == PROP_DEPLOYMENT ? "deployment" :
606 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
608 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
609 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
610 prop == PROP_DEPLOYMENT ? "Deployment" :
611 prop == PROP_CHASSIS ? "Chassis" : "IconName" , NULL);
613 return sd_bus_reply_method_return(m, NULL);
616 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
617 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
620 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
621 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
624 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
625 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
628 static int method_set_deployment(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
629 return set_machine_info(userdata, bus, m, PROP_DEPLOYMENT, method_set_deployment, error);
632 static const sd_bus_vtable hostname_vtable[] = {
633 SD_BUS_VTABLE_START(0),
634 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
635 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
636 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
637 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
638 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
639 SD_BUS_PROPERTY("Deployment", "s", property_get_deployment, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
640 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
641 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
642 SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
643 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
644 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
645 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
646 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
647 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
648 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
649 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
650 SD_BUS_METHOD("SetDeployment", "sb", NULL, method_set_deployment, SD_BUS_VTABLE_UNPRIVILEGED),
654 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
655 _cleanup_bus_unref_ sd_bus *bus = NULL;
662 r = sd_bus_default_system(&bus);
664 log_error("Failed to get system bus connection: %s", strerror(-r));
668 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
670 log_error("Failed to register object: %s", strerror(-r));
674 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
676 log_error("Failed to register name: %s", strerror(-r));
680 r = sd_bus_attach_event(bus, event, 0);
682 log_error("Failed to attach bus to event loop: %s", strerror(-r));
692 int main(int argc, char *argv[]) {
693 Context context = {};
695 _cleanup_event_unref_ sd_event *event = NULL;
696 _cleanup_bus_unref_ sd_bus *bus = NULL;
699 log_set_target(LOG_TARGET_AUTO);
700 log_parse_environment();
707 log_error("This program takes no arguments.");
713 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
716 log_error("This program takes no arguments.");
721 r = sd_event_default(&event);
723 log_error("Failed to allocate event loop: %s", strerror(-r));
727 sd_event_set_watchdog(event, true);
729 r = connect_bus(&context, event, &bus);
733 r = context_read_data(&context);
735 log_error("Failed to read hostname and machine information: %s", strerror(-r));
739 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
741 log_error("Failed to run event loop: %s", strerror(-r));
746 context_free(&context, bus);
748 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;