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/>.
25 #include <sys/utsname.h>
32 #include "fileio-label.h"
34 #include "event-util.h"
35 #include "selinux-util.h"
37 #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) {
75 bus_verify_polkit_async_registry_free(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],
107 "LOCATION", &c->data[PROP_LOCATION],
109 if (r < 0 && r != -ENOENT)
112 r = parse_env_file("/etc/os-release", NEWLINE,
113 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
114 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
117 r = parse_env_file("/usr/lib/os-release", NEWLINE,
118 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
119 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
123 if (r < 0 && r != -ENOENT)
129 static bool valid_chassis(const char *chassis) {
132 return nulstr_contains(
145 static bool valid_deployment(const char *deployment) {
148 return in_charset(deployment, VALID_DEPLOYMENT_CHARS);
151 static const char* fallback_chassis(void) {
157 v = detect_virtualization(NULL);
159 if (v == VIRTUALIZATION_VM)
161 if (v == VIRTUALIZATION_CONTAINER)
164 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
168 r = safe_atou(type, &t);
173 /* We only list the really obvious cases here as the ACPI data
174 * is not really super reliable.
176 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
178 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
201 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
205 r = safe_atou(type, &t);
210 /* We only list the really obvious cases here. The DMI data is
211 unreliable enough, so let's not do any additional guesswork
214 See the SMBIOS Specification 2.7.1 section 7.4.1 for
215 details about the values listed here:
217 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
245 static char* context_fallback_icon_name(Context *c) {
250 if (!isempty(c->data[PROP_CHASSIS]))
251 return strappend("computer-", c->data[PROP_CHASSIS]);
253 chassis = fallback_chassis();
255 return strappend("computer-", chassis);
257 return strdup("computer");
261 static bool hostname_is_useful(const char *hn) {
262 return !isempty(hn) && !is_localhost(hn);
265 static int context_update_kernel_hostname(Context *c) {
266 const char *static_hn;
271 static_hn = c->data[PROP_STATIC_HOSTNAME];
273 /* /etc/hostname with something other than "localhost"
274 * has the highest preference ... */
275 if (hostname_is_useful(static_hn))
278 /* ... the transient host name, (ie: DHCP) comes next ... */
279 else if (!isempty(c->data[PROP_HOSTNAME]))
280 hn = c->data[PROP_HOSTNAME];
282 /* ... fallback to static "localhost.*" ignored above ... */
283 else if (!isempty(static_hn))
286 /* ... and the ultimate fallback */
290 if (sethostname_idempotent(hn) < 0)
296 static int context_write_data_static_hostname(Context *c) {
300 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
302 if (unlink("/etc/hostname") < 0)
303 return errno == ENOENT ? 0 : -errno;
307 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
310 static int context_write_data_machine_info(Context *c) {
312 static const char * const name[_PROP_MAX] = {
313 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
314 [PROP_ICON_NAME] = "ICON_NAME",
315 [PROP_CHASSIS] = "CHASSIS",
316 [PROP_DEPLOYMENT] = "DEPLOYMENT",
317 [PROP_LOCATION] = "LOCATION",
320 _cleanup_strv_free_ char **l = NULL;
325 r = load_env_file(NULL, "/etc/machine-info", NULL, &l);
326 if (r < 0 && r != -ENOENT)
329 for (p = PROP_PRETTY_HOSTNAME; p <= PROP_LOCATION; p++) {
330 _cleanup_free_ char *t = NULL;
335 if (isempty(c->data[p])) {
336 strv_env_unset(l, name[p]);
340 t = strjoin(name[p], "=", c->data[p], NULL);
344 u = strv_env_set(l, t);
352 if (strv_isempty(l)) {
353 if (unlink("/etc/machine-info") < 0)
354 return errno == ENOENT ? 0 : -errno;
359 return write_env_file_label("/etc/machine-info", l);
362 static int property_get_icon_name(
365 const char *interface,
366 const char *property,
367 sd_bus_message *reply,
369 sd_bus_error *error) {
371 _cleanup_free_ char *n = NULL;
372 Context *c = userdata;
375 if (isempty(c->data[PROP_ICON_NAME]))
376 name = n = context_fallback_icon_name(c);
378 name = c->data[PROP_ICON_NAME];
383 return sd_bus_message_append(reply, "s", name);
386 static int property_get_chassis(
389 const char *interface,
390 const char *property,
391 sd_bus_message *reply,
393 sd_bus_error *error) {
395 Context *c = userdata;
398 if (isempty(c->data[PROP_CHASSIS]))
399 name = fallback_chassis();
401 name = c->data[PROP_CHASSIS];
403 return sd_bus_message_append(reply, "s", name);
406 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
407 Context *c = userdata;
413 r = sd_bus_message_read(m, "sb", &name, &interactive);
418 name = c->data[PROP_STATIC_HOSTNAME];
423 if (!hostname_is_valid(name))
424 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
426 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
427 return sd_bus_reply_method_return(m, NULL);
429 r = bus_verify_polkit_async(
432 "org.freedesktop.hostname1.set-hostname",
440 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
446 free(c->data[PROP_HOSTNAME]);
447 c->data[PROP_HOSTNAME] = h;
449 r = context_update_kernel_hostname(c);
451 log_error_errno(r, "Failed to set host name: %m");
452 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
455 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
457 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
459 return sd_bus_reply_method_return(m, NULL);
462 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
463 Context *c = userdata;
468 r = sd_bus_message_read(m, "sb", &name, &interactive);
475 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
476 return sd_bus_reply_method_return(m, NULL);
478 r = bus_verify_polkit_async(
481 "org.freedesktop.hostname1.set-static-hostname",
489 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
492 free(c->data[PROP_STATIC_HOSTNAME]);
493 c->data[PROP_STATIC_HOSTNAME] = NULL;
497 if (!hostname_is_valid(name))
498 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
504 free(c->data[PROP_STATIC_HOSTNAME]);
505 c->data[PROP_STATIC_HOSTNAME] = h;
508 r = context_update_kernel_hostname(c);
510 log_error_errno(r, "Failed to set host name: %m");
511 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
514 r = context_write_data_static_hostname(c);
516 log_error_errno(r, "Failed to write static host name: %m");
517 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
520 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
522 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
524 return sd_bus_reply_method_return(m, NULL);
527 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) {
536 r = sd_bus_message_read(m, "sb", &name, &interactive);
543 if (streq_ptr(name, c->data[prop]))
544 return sd_bus_reply_method_return(m, NULL);
546 /* Since the pretty hostname should always be changed at the
547 * same time as the static one, use the same policy action for
550 r = bus_verify_polkit_async(
553 prop == PROP_PRETTY_HOSTNAME ? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
561 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
565 c->data[prop] = NULL;
569 /* The icon name might ultimately be used as file
570 * name, so better be safe than sorry */
572 if (prop == PROP_ICON_NAME && !filename_is_valid(name))
573 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
574 if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL))
575 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
576 if (prop == PROP_CHASSIS && !valid_chassis(name))
577 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
578 if (prop == PROP_DEPLOYMENT && !valid_deployment(name))
579 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name);
580 if (prop == PROP_LOCATION && string_has_cc(name, NULL))
581 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid location '%s'", name);
591 r = context_write_data_machine_info(c);
593 log_error_errno(r, "Failed to write machine info: %m");
594 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
597 log_info("Changed %s to '%s'",
598 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
599 prop == PROP_DEPLOYMENT ? "deployment" :
600 prop == PROP_LOCATION ? "location" :
601 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
603 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
604 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
605 prop == PROP_DEPLOYMENT ? "Deployment" :
606 prop == PROP_LOCATION ? "Location" :
607 prop == PROP_CHASSIS ? "Chassis" : "IconName" , NULL);
609 return sd_bus_reply_method_return(m, NULL);
612 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
613 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
616 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
617 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
620 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
621 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
624 static int method_set_deployment(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
625 return set_machine_info(userdata, bus, m, PROP_DEPLOYMENT, method_set_deployment, error);
628 static int method_set_location(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
629 return set_machine_info(userdata, bus, m, PROP_LOCATION, method_set_location, 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, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
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", NULL, offsetof(Context, data) + sizeof(char*) * PROP_DEPLOYMENT, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
640 SD_BUS_PROPERTY("Location", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_LOCATION, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
641 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
642 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
643 SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
644 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
645 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
646 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
647 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
648 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
649 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
650 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
651 SD_BUS_METHOD("SetDeployment", "sb", NULL, method_set_deployment, SD_BUS_VTABLE_UNPRIVILEGED),
652 SD_BUS_METHOD("SetLocation", "sb", NULL, method_set_location, SD_BUS_VTABLE_UNPRIVILEGED),
656 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
657 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
664 r = sd_bus_default_system(&bus);
666 return log_error_errno(r, "Failed to get system bus connection: %m");
668 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
670 return log_error_errno(r, "Failed to register object: %m");
672 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
674 return log_error_errno(r, "Failed to register name: %m");
676 r = sd_bus_attach_event(bus, event, 0);
678 return log_error_errno(r, "Failed to attach bus to event loop: %m");
686 int main(int argc, char *argv[]) {
687 Context context = {};
688 _cleanup_event_unref_ sd_event *event = NULL;
689 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
692 log_set_target(LOG_TARGET_AUTO);
693 log_parse_environment();
697 mac_selinux_init("/etc");
700 log_error("This program takes no arguments.");
706 log_error("This program takes no arguments.");
711 r = sd_event_default(&event);
713 log_error_errno(r, "Failed to allocate event loop: %m");
717 sd_event_set_watchdog(event, true);
719 r = connect_bus(&context, event, &bus);
723 r = context_read_data(&context);
725 log_error_errno(r, "Failed to read hostname and machine information: %m");
729 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
731 log_error_errno(r, "Failed to run event loop: %m");
736 context_free(&context);
738 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;