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"
48 typedef struct Context {
49 char *data[_PROP_MAX];
50 Hashmap *polkit_registry;
53 static void context_reset(Context *c) {
58 for (p = 0; p < _PROP_MAX; p++) {
64 static void context_free(Context *c, sd_bus *bus) {
68 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
71 static int context_read_data(Context *c) {
78 c->data[PROP_HOSTNAME] = gethostname_malloc();
79 if (!c->data[PROP_HOSTNAME])
82 r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
83 if (r < 0 && r != -ENOENT)
86 r = parse_env_file("/etc/machine-info", NEWLINE,
87 "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
88 "ICON_NAME", &c->data[PROP_ICON_NAME],
89 "CHASSIS", &c->data[PROP_CHASSIS],
91 if (r < 0 && r != -ENOENT)
94 r = parse_env_file("/etc/os-release", NEWLINE,
95 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
96 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
98 if (r < 0 && r != -ENOENT)
104 static bool check_nss(void) {
107 dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
116 static bool valid_chassis(const char *chassis) {
120 return nulstr_contains(
131 static const char* fallback_chassis(void) {
137 v = detect_virtualization(NULL);
139 if (v == VIRTUALIZATION_VM)
141 if (v == VIRTUALIZATION_CONTAINER)
144 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
148 r = safe_atou(type, &t);
153 /* We only list the really obvious cases here as the ACPI data
154 * is not really super reliable.
156 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
158 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
181 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
185 r = safe_atou(type, &t);
190 /* We only list the really obvious cases here. The DMI data is
191 unreliable enough, so let's not do any additional guesswork
194 See the SMBIOS Specification 2.7.1 section 7.4.1 for
195 details about the values listed here:
197 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
225 static char* context_fallback_icon_name(Context *c) {
230 if (!isempty(c->data[PROP_CHASSIS]))
231 return strappend("computer-", c->data[PROP_CHASSIS]);
233 chassis = fallback_chassis();
235 return strappend("computer-", chassis);
237 return strdup("computer");
240 static int context_write_data_hostname(Context *c) {
245 if (isempty(c->data[PROP_HOSTNAME]))
248 hn = c->data[PROP_HOSTNAME];
250 if (sethostname(hn, strlen(hn)) < 0)
256 static int context_write_data_static_hostname(Context *c) {
260 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
262 if (unlink("/etc/hostname") < 0)
263 return errno == ENOENT ? 0 : -errno;
267 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
270 static int context_write_data_machine_info(Context *c) {
272 static const char * const name[_PROP_MAX] = {
273 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
274 [PROP_ICON_NAME] = "ICON_NAME",
275 [PROP_CHASSIS] = "CHASSIS"
278 _cleanup_strv_free_ char **l = NULL;
283 r = load_env_file("/etc/machine-info", NULL, &l);
284 if (r < 0 && r != -ENOENT)
287 for (p = PROP_PRETTY_HOSTNAME; p <= PROP_CHASSIS; p++) {
292 if (isempty(c->data[p])) {
293 strv_env_unset(l, name[p]);
297 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0)
300 u = strv_env_set(l, t);
310 if (strv_isempty(l)) {
312 if (unlink("/etc/machine-info") < 0)
313 return errno == ENOENT ? 0 : -errno;
318 return write_env_file_label("/etc/machine-info", l);
321 static int property_get_icon_name(
324 const char *interface,
325 const char *property,
326 sd_bus_message *reply,
328 sd_bus_error *error) {
330 _cleanup_free_ char *n = NULL;
331 Context *c = userdata;
334 if (isempty(c->data[PROP_ICON_NAME]))
335 name = n = context_fallback_icon_name(c);
337 name = c->data[PROP_ICON_NAME];
342 return sd_bus_message_append(reply, "s", name);
345 static int property_get_chassis(
348 const char *interface,
349 const char *property,
350 sd_bus_message *reply,
352 sd_bus_error *error) {
354 Context *c = userdata;
357 if (isempty(c->data[PROP_CHASSIS]))
358 name = fallback_chassis();
360 name = c->data[PROP_CHASSIS];
362 return sd_bus_message_append(reply, "s", name);
365 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
366 Context *c = userdata;
372 r = sd_bus_message_read(m, "sb", &name, &interactive);
377 name = c->data[PROP_STATIC_HOSTNAME];
382 if (!hostname_is_valid(name))
383 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
385 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
386 return sd_bus_reply_method_return(m, NULL);
388 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
392 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
398 free(c->data[PROP_HOSTNAME]);
399 c->data[PROP_HOSTNAME] = h;
401 r = context_write_data_hostname(c);
403 log_error("Failed to set host name: %s", strerror(-r));
404 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
407 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
409 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
411 return sd_bus_reply_method_return(m, NULL);
414 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
415 Context *c = userdata;
420 r = sd_bus_message_read(m, "sb", &name, &interactive);
427 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
428 return sd_bus_reply_method_return(m, NULL);
430 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
434 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
437 free(c->data[PROP_STATIC_HOSTNAME]);
438 c->data[PROP_STATIC_HOSTNAME] = NULL;
442 if (!hostname_is_valid(name))
443 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
449 free(c->data[PROP_STATIC_HOSTNAME]);
450 c->data[PROP_STATIC_HOSTNAME] = h;
453 r = context_write_data_static_hostname(c);
455 log_error("Failed to write static host name: %s", strerror(-r));
456 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
459 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
461 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
463 return sd_bus_reply_method_return(m, NULL);
466 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) {
475 r = sd_bus_message_read(m, "sb", &name, &interactive);
482 if (streq_ptr(name, c->data[prop]))
483 return sd_bus_reply_method_return(m, NULL);
485 /* Since the pretty hostname should always be changed at the
486 * same time as the static one, use the same policy action for
489 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
490 "org.freedesktop.hostname1.set-static-hostname" :
491 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
495 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
499 c->data[prop] = NULL;
503 /* The icon name might ultimately be used as file
504 * name, so better be safe than sorry */
506 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
507 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
508 if (prop == PROP_PRETTY_HOSTNAME &&
509 (string_has_cc(name) || chars_intersect(name, "\t")))
510 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
511 if (prop == PROP_CHASSIS && !valid_chassis(name))
512 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
522 r = context_write_data_machine_info(c);
524 log_error("Failed to write machine info: %s", strerror(-r));
525 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
528 log_info("Changed %s to '%s'",
529 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
530 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
532 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
533 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
534 prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
536 return sd_bus_reply_method_return(m, NULL);
539 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
540 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
543 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
544 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
547 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
548 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
551 static const sd_bus_vtable hostname_vtable[] = {
552 SD_BUS_VTABLE_START(0),
553 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
554 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
555 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
556 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
557 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
558 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
559 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
560 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
561 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
562 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
563 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
564 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
568 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
569 _cleanup_bus_unref_ sd_bus *bus = NULL;
576 r = sd_bus_default_system(&bus);
578 log_error("Failed to get system bus connection: %s", strerror(-r));
582 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
584 log_error("Failed to register object: %s", strerror(-r));
588 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
590 log_error("Failed to register name: %s", strerror(-r));
594 r = sd_bus_attach_event(bus, event, 0);
596 log_error("Failed to attach bus to event loop: %s", strerror(-r));
606 int main(int argc, char *argv[]) {
607 Context context = {};
609 _cleanup_event_unref_ sd_event *event = NULL;
610 _cleanup_bus_unref_ sd_bus *bus = NULL;
613 log_set_target(LOG_TARGET_AUTO);
614 log_parse_environment();
621 log_error("This program takes no arguments.");
627 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
630 log_error("This program takes no arguments.");
635 r = sd_event_default(&event);
637 log_error("Failed to allocate event loop: %s", strerror(-r));
641 sd_event_set_watchdog(event, true);
643 r = connect_bus(&context, event, &bus);
647 r = context_read_data(&context);
649 log_error("Failed to read hostname and machine information: %s", strerror(-r));
653 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
655 log_error("Failed to run event loop: %s", strerror(-r));
660 context_free(&context, bus);
662 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;