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/>.
32 #include "fileio-label.h"
35 #include "event-util.h"
46 typedef struct Context {
47 char *data[_PROP_MAX];
48 Hashmap *polkit_registry;
51 static void context_reset(Context *c) {
56 for (p = 0; p < _PROP_MAX; p++) {
62 static void context_free(Context *c, sd_bus *bus) {
66 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
69 static int context_read_data(Context *c) {
76 c->data[PROP_HOSTNAME] = gethostname_malloc();
77 if (!c->data[PROP_HOSTNAME])
80 r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
81 if (r < 0 && r != -ENOENT)
84 r = parse_env_file("/etc/machine-info", NEWLINE,
85 "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
86 "ICON_NAME", &c->data[PROP_ICON_NAME],
87 "CHASSIS", &c->data[PROP_CHASSIS],
89 if (r < 0 && r != -ENOENT)
95 static bool check_nss(void) {
98 dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
107 static bool valid_chassis(const char *chassis) {
111 return nulstr_contains(
122 static const char* fallback_chassis(void) {
128 v = detect_virtualization(NULL);
130 if (v == VIRTUALIZATION_VM)
132 if (v == VIRTUALIZATION_CONTAINER)
135 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
139 r = safe_atou(type, &t);
144 /* We only list the really obvious cases here as the ACPI data
145 * is not really super reliable.
147 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
149 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
172 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
176 r = safe_atou(type, &t);
181 /* We only list the really obvious cases here. The DMI data is
182 unreliable enough, so let's not do any additional guesswork
185 See the SMBIOS Specification 2.7.1 section 7.4.1 for
186 details about the values listed here:
188 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
216 static char* context_fallback_icon_name(Context *c) {
221 if (!isempty(c->data[PROP_CHASSIS]))
222 return strappend("computer-", c->data[PROP_CHASSIS]);
224 chassis = fallback_chassis();
226 return strappend("computer-", chassis);
228 return strdup("computer");
231 static int context_write_data_hostname(Context *c) {
236 if (isempty(c->data[PROP_HOSTNAME]))
239 hn = c->data[PROP_HOSTNAME];
241 if (sethostname(hn, strlen(hn)) < 0)
247 static int context_write_data_static_hostname(Context *c) {
251 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
253 if (unlink("/etc/hostname") < 0)
254 return errno == ENOENT ? 0 : -errno;
258 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
261 static int context_write_data_other(Context *c) {
263 static const char * const name[_PROP_MAX] = {
264 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
265 [PROP_ICON_NAME] = "ICON_NAME",
266 [PROP_CHASSIS] = "CHASSIS"
269 _cleanup_strv_free_ char **l = NULL;
274 r = load_env_file("/etc/machine-info", NULL, &l);
275 if (r < 0 && r != -ENOENT)
278 for (p = 2; p < _PROP_MAX; p++) {
283 if (isempty(c->data[p])) {
284 strv_env_unset(l, name[p]);
288 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0)
291 u = strv_env_set(l, t);
301 if (strv_isempty(l)) {
303 if (unlink("/etc/machine-info") < 0)
304 return errno == ENOENT ? 0 : -errno;
309 return write_env_file_label("/etc/machine-info", l);
312 static int property_get_icon_name(
315 const char *interface,
316 const char *property,
317 sd_bus_message *reply,
319 sd_bus_error *error) {
321 _cleanup_free_ char *n = NULL;
322 Context *c = userdata;
325 if (isempty(c->data[PROP_ICON_NAME]))
326 name = n = context_fallback_icon_name(c);
328 name = c->data[PROP_ICON_NAME];
333 return sd_bus_message_append(reply, "s", name);
336 static int property_get_chassis(
339 const char *interface,
340 const char *property,
341 sd_bus_message *reply,
343 sd_bus_error *error) {
345 Context *c = userdata;
348 if (isempty(c->data[PROP_CHASSIS]))
349 name = fallback_chassis();
351 name = c->data[PROP_CHASSIS];
353 return sd_bus_message_append(reply, "s", name);
356 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
357 Context *c = userdata;
363 r = sd_bus_message_read(m, "sb", &name, &interactive);
368 name = c->data[PROP_STATIC_HOSTNAME];
373 if (!hostname_is_valid(name))
374 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
376 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
377 return sd_bus_reply_method_return(m, NULL);
379 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
383 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
389 free(c->data[PROP_HOSTNAME]);
390 c->data[PROP_HOSTNAME] = h;
392 r = context_write_data_hostname(c);
394 log_error("Failed to set host name: %s", strerror(-r));
395 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
398 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
400 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
402 return sd_bus_reply_method_return(m, NULL);
405 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
406 Context *c = userdata;
411 r = sd_bus_message_read(m, "sb", &name, &interactive);
418 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
419 return sd_bus_reply_method_return(m, NULL);
421 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
425 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
428 free(c->data[PROP_STATIC_HOSTNAME]);
429 c->data[PROP_STATIC_HOSTNAME] = NULL;
433 if (!hostname_is_valid(name))
434 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
440 free(c->data[PROP_STATIC_HOSTNAME]);
441 c->data[PROP_STATIC_HOSTNAME] = h;
444 r = context_write_data_static_hostname(c);
446 log_error("Failed to write static host name: %s", strerror(-r));
447 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
450 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
452 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
454 return sd_bus_reply_method_return(m, NULL);
457 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) {
466 r = sd_bus_message_read(m, "sb", &name, &interactive);
473 if (streq_ptr(name, c->data[prop]))
474 return sd_bus_reply_method_return(m, NULL);
476 /* Since the pretty hostname should always be changed at the
477 * same time as the static one, use the same policy action for
480 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
481 "org.freedesktop.hostname1.set-static-hostname" :
482 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
486 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
490 c->data[prop] = NULL;
494 /* The icon name might ultimately be used as file
495 * name, so better be safe than sorry */
497 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
498 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
499 if (prop == PROP_PRETTY_HOSTNAME &&
500 (string_has_cc(name) || chars_intersect(name, "\t")))
501 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
502 if (prop == PROP_CHASSIS && !valid_chassis(name))
503 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
513 r = context_write_data_other(c);
515 log_error("Failed to write machine info: %s", strerror(-r));
516 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
519 log_info("Changed %s to '%s'",
520 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
521 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
523 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
524 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
525 prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
527 return sd_bus_reply_method_return(m, NULL);
530 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
531 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
534 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
535 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
538 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
539 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
542 static const sd_bus_vtable hostname_vtable[] = {
543 SD_BUS_VTABLE_START(0),
544 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
545 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
546 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
547 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
548 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
549 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
550 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
551 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
552 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
553 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
557 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
558 _cleanup_bus_unref_ sd_bus *bus = NULL;
565 r = sd_bus_default_system(&bus);
567 log_error("Failed to get system bus connection: %s", strerror(-r));
571 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
573 log_error("Failed to register object: %s", strerror(-r));
577 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
579 log_error("Failed to register name: %s", strerror(-r));
583 r = sd_bus_attach_event(bus, event, 0);
585 log_error("Failed to attach bus to event loop: %s", strerror(-r));
595 int main(int argc, char *argv[]) {
596 Context context = {};
598 _cleanup_event_unref_ sd_event *event = NULL;
599 _cleanup_bus_unref_ sd_bus *bus = NULL;
602 log_set_target(LOG_TARGET_AUTO);
603 log_parse_environment();
610 log_error("This program takes no arguments.");
616 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
619 log_error("This program takes no arguments.");
624 r = sd_event_default(&event);
626 log_error("Failed to allocate event loop: %s", strerror(-r));
630 sd_event_set_watchdog(event, true);
632 r = connect_bus(&context, event, &bus);
636 r = context_read_data(&context);
638 log_error("Failed to read timezone data: %s", strerror(-r));
642 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
644 log_error("Failed to run event loop: %s", strerror(-r));
649 context_free(&context, bus);
651 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;