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 int context_write_data_hostname(Context *c) {
258 if (isempty(c->data[PROP_HOSTNAME]))
261 hn = c->data[PROP_HOSTNAME];
263 if (sethostname(hn, strlen(hn)) < 0)
269 static int context_write_data_static_hostname(Context *c) {
273 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
275 if (unlink("/etc/hostname") < 0)
276 return errno == ENOENT ? 0 : -errno;
280 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
283 static int context_write_data_machine_info(Context *c) {
285 static const char * const name[_PROP_MAX] = {
286 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
287 [PROP_ICON_NAME] = "ICON_NAME",
288 [PROP_CHASSIS] = "CHASSIS"
291 _cleanup_strv_free_ char **l = NULL;
296 r = load_env_file("/etc/machine-info", NULL, &l);
297 if (r < 0 && r != -ENOENT)
300 for (p = PROP_PRETTY_HOSTNAME; p <= PROP_CHASSIS; p++) {
305 if (isempty(c->data[p])) {
306 strv_env_unset(l, name[p]);
310 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0)
313 u = strv_env_set(l, t);
323 if (strv_isempty(l)) {
325 if (unlink("/etc/machine-info") < 0)
326 return errno == ENOENT ? 0 : -errno;
331 return write_env_file_label("/etc/machine-info", l);
334 static int property_get_icon_name(
337 const char *interface,
338 const char *property,
339 sd_bus_message *reply,
341 sd_bus_error *error) {
343 _cleanup_free_ char *n = NULL;
344 Context *c = userdata;
347 if (isempty(c->data[PROP_ICON_NAME]))
348 name = n = context_fallback_icon_name(c);
350 name = c->data[PROP_ICON_NAME];
355 return sd_bus_message_append(reply, "s", name);
358 static int property_get_chassis(
361 const char *interface,
362 const char *property,
363 sd_bus_message *reply,
365 sd_bus_error *error) {
367 Context *c = userdata;
370 if (isempty(c->data[PROP_CHASSIS]))
371 name = fallback_chassis();
373 name = c->data[PROP_CHASSIS];
375 return sd_bus_message_append(reply, "s", name);
378 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
379 Context *c = userdata;
385 r = sd_bus_message_read(m, "sb", &name, &interactive);
390 name = c->data[PROP_STATIC_HOSTNAME];
395 if (!hostname_is_valid(name))
396 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
398 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
399 return sd_bus_reply_method_return(m, NULL);
401 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
405 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
411 free(c->data[PROP_HOSTNAME]);
412 c->data[PROP_HOSTNAME] = h;
414 r = context_write_data_hostname(c);
416 log_error("Failed to set host name: %s", strerror(-r));
417 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
420 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
422 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
424 return sd_bus_reply_method_return(m, NULL);
427 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
428 Context *c = userdata;
433 r = sd_bus_message_read(m, "sb", &name, &interactive);
440 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
441 return sd_bus_reply_method_return(m, NULL);
443 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
447 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
450 free(c->data[PROP_STATIC_HOSTNAME]);
451 c->data[PROP_STATIC_HOSTNAME] = NULL;
455 if (!hostname_is_valid(name))
456 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
462 free(c->data[PROP_STATIC_HOSTNAME]);
463 c->data[PROP_STATIC_HOSTNAME] = h;
466 r = context_write_data_static_hostname(c);
468 log_error("Failed to write static host name: %s", strerror(-r));
469 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
472 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
474 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
476 return sd_bus_reply_method_return(m, NULL);
479 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) {
488 r = sd_bus_message_read(m, "sb", &name, &interactive);
495 if (streq_ptr(name, c->data[prop]))
496 return sd_bus_reply_method_return(m, NULL);
498 /* Since the pretty hostname should always be changed at the
499 * same time as the static one, use the same policy action for
502 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
503 "org.freedesktop.hostname1.set-static-hostname" :
504 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
508 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
512 c->data[prop] = NULL;
516 /* The icon name might ultimately be used as file
517 * name, so better be safe than sorry */
519 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
520 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
521 if (prop == PROP_PRETTY_HOSTNAME &&
522 (string_has_cc(name) || chars_intersect(name, "\t")))
523 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
524 if (prop == PROP_CHASSIS && !valid_chassis(name))
525 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
535 r = context_write_data_machine_info(c);
537 log_error("Failed to write machine info: %s", strerror(-r));
538 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
541 log_info("Changed %s to '%s'",
542 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
543 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
545 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
546 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
547 prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
549 return sd_bus_reply_method_return(m, NULL);
552 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
553 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
556 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
557 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
560 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
561 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
564 static const sd_bus_vtable hostname_vtable[] = {
565 SD_BUS_VTABLE_START(0),
566 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
567 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
568 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
569 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
570 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
571 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
572 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
573 SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
574 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
575 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
576 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
577 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
578 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
579 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
580 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
584 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
585 _cleanup_bus_unref_ sd_bus *bus = NULL;
592 r = sd_bus_default_system(&bus);
594 log_error("Failed to get system bus connection: %s", strerror(-r));
598 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
600 log_error("Failed to register object: %s", strerror(-r));
604 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
606 log_error("Failed to register name: %s", strerror(-r));
610 r = sd_bus_attach_event(bus, event, 0);
612 log_error("Failed to attach bus to event loop: %s", strerror(-r));
622 int main(int argc, char *argv[]) {
623 Context context = {};
625 _cleanup_event_unref_ sd_event *event = NULL;
626 _cleanup_bus_unref_ sd_bus *bus = NULL;
629 log_set_target(LOG_TARGET_AUTO);
630 log_parse_environment();
637 log_error("This program takes no arguments.");
643 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
646 log_error("This program takes no arguments.");
651 r = sd_event_default(&event);
653 log_error("Failed to allocate event loop: %s", strerror(-r));
657 sd_event_set_watchdog(event, true);
659 r = connect_bus(&context, event, &bus);
663 r = context_read_data(&context);
665 log_error("Failed to read hostname and machine information: %s", strerror(-r));
669 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
671 log_error("Failed to run event loop: %s", strerror(-r));
676 context_free(&context, bus);
678 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;