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"
35 #include "event-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, 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],
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(
144 static bool valid_deployment(const char *deployment) {
147 return in_charset(deployment, VALID_DEPLOYMENT_CHARS);
150 static const char* fallback_chassis(void) {
156 v = detect_virtualization(NULL);
158 if (v == VIRTUALIZATION_VM)
160 if (v == VIRTUALIZATION_CONTAINER)
163 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
167 r = safe_atou(type, &t);
172 /* We only list the really obvious cases here as the ACPI data
173 * is not really super reliable.
175 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
177 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
200 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
204 r = safe_atou(type, &t);
209 /* We only list the really obvious cases here. The DMI data is
210 unreliable enough, so let's not do any additional guesswork
213 See the SMBIOS Specification 2.7.1 section 7.4.1 for
214 details about the values listed here:
216 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
244 static char* context_fallback_icon_name(Context *c) {
249 if (!isempty(c->data[PROP_CHASSIS]))
250 return strappend("computer-", c->data[PROP_CHASSIS]);
252 chassis = fallback_chassis();
254 return strappend("computer-", chassis);
256 return strdup("computer");
260 static bool hostname_is_useful(const char *hn) {
261 return !isempty(hn) && !is_localhost(hn);
264 static int context_update_kernel_hostname(Context *c) {
265 const char *static_hn;
270 static_hn = c->data[PROP_STATIC_HOSTNAME];
272 /* /etc/hostname with something other than "localhost"
273 * has the highest preference ... */
274 if (hostname_is_useful(static_hn))
277 /* ... the transient host name, (ie: DHCP) comes next ...*/
278 else if (!isempty(c->data[PROP_HOSTNAME]))
279 hn = c->data[PROP_HOSTNAME];
281 /* ... fallback to static "localhost.*" ignored above ... */
282 else if (!isempty(static_hn))
285 /* ... and the ultimate fallback */
289 if (sethostname(hn, strlen(hn)) < 0)
295 static int context_write_data_static_hostname(Context *c) {
299 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
301 if (unlink("/etc/hostname") < 0)
302 return errno == ENOENT ? 0 : -errno;
306 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
309 static int context_write_data_machine_info(Context *c) {
311 static const char * const name[_PROP_MAX] = {
312 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
313 [PROP_ICON_NAME] = "ICON_NAME",
314 [PROP_CHASSIS] = "CHASSIS",
315 [PROP_DEPLOYMENT] = "DEPLOYMENT",
316 [PROP_LOCATION] = "LOCATION",
319 _cleanup_strv_free_ char **l = NULL;
324 r = load_env_file(NULL, "/etc/machine-info", NULL, &l);
325 if (r < 0 && r != -ENOENT)
328 for (p = PROP_PRETTY_HOSTNAME; p <= PROP_LOCATION; p++) {
329 _cleanup_free_ char *t = NULL;
334 if (isempty(c->data[p])) {
335 strv_env_unset(l, name[p]);
339 t = strjoin(name[p], "=", c->data[p], NULL);
343 u = strv_env_set(l, t);
351 if (strv_isempty(l)) {
352 if (unlink("/etc/machine-info") < 0)
353 return errno == ENOENT ? 0 : -errno;
358 return write_env_file_label("/etc/machine-info", l);
361 static int property_get_icon_name(
364 const char *interface,
365 const char *property,
366 sd_bus_message *reply,
368 sd_bus_error *error) {
370 _cleanup_free_ char *n = NULL;
371 Context *c = userdata;
374 if (isempty(c->data[PROP_ICON_NAME]))
375 name = n = context_fallback_icon_name(c);
377 name = c->data[PROP_ICON_NAME];
382 return sd_bus_message_append(reply, "s", name);
385 static int property_get_chassis(
388 const char *interface,
389 const char *property,
390 sd_bus_message *reply,
392 sd_bus_error *error) {
394 Context *c = userdata;
397 if (isempty(c->data[PROP_CHASSIS]))
398 name = fallback_chassis();
400 name = c->data[PROP_CHASSIS];
402 return sd_bus_message_append(reply, "s", name);
405 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
406 Context *c = userdata;
412 r = sd_bus_message_read(m, "sb", &name, &interactive);
417 name = c->data[PROP_STATIC_HOSTNAME];
422 if (!hostname_is_valid(name))
423 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
425 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
426 return sd_bus_reply_method_return(m, NULL);
428 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
432 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
438 free(c->data[PROP_HOSTNAME]);
439 c->data[PROP_HOSTNAME] = h;
441 r = context_update_kernel_hostname(c);
443 log_error("Failed to set host name: %s", strerror(-r));
444 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
447 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
449 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
451 return sd_bus_reply_method_return(m, NULL);
454 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
455 Context *c = userdata;
460 r = sd_bus_message_read(m, "sb", &name, &interactive);
467 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
468 return sd_bus_reply_method_return(m, NULL);
470 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
474 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
477 free(c->data[PROP_STATIC_HOSTNAME]);
478 c->data[PROP_STATIC_HOSTNAME] = NULL;
482 if (!hostname_is_valid(name))
483 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
489 free(c->data[PROP_STATIC_HOSTNAME]);
490 c->data[PROP_STATIC_HOSTNAME] = h;
493 r = context_update_kernel_hostname(c);
495 log_error("Failed to set host name: %s", strerror(-r));
496 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
499 r = context_write_data_static_hostname(c);
501 log_error("Failed to write static host name: %s", strerror(-r));
502 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
505 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
507 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
509 return sd_bus_reply_method_return(m, NULL);
512 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) {
521 r = sd_bus_message_read(m, "sb", &name, &interactive);
528 if (streq_ptr(name, c->data[prop]))
529 return sd_bus_reply_method_return(m, NULL);
531 /* Since the pretty hostname should always be changed at the
532 * same time as the static one, use the same policy action for
535 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
536 "org.freedesktop.hostname1.set-static-hostname" :
537 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
541 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
545 c->data[prop] = NULL;
549 /* The icon name might ultimately be used as file
550 * name, so better be safe than sorry */
552 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
553 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
554 if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL))
555 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
556 if (prop == PROP_CHASSIS && !valid_chassis(name))
557 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
558 if (prop == PROP_DEPLOYMENT && !valid_deployment(name))
559 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name);
560 if (prop == PROP_LOCATION && string_has_cc(name, NULL))
561 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid location '%s'", name);
571 r = context_write_data_machine_info(c);
573 log_error("Failed to write machine info: %s", strerror(-r));
574 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
577 log_info("Changed %s to '%s'",
578 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
579 prop == PROP_DEPLOYMENT ? "deployment" :
580 prop == PROP_LOCATION ? "location" :
581 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
583 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
584 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
585 prop == PROP_DEPLOYMENT ? "Deployment" :
586 prop == PROP_LOCATION ? "Location" :
587 prop == PROP_CHASSIS ? "Chassis" : "IconName" , NULL);
589 return sd_bus_reply_method_return(m, NULL);
592 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
593 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
596 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
597 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
600 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
601 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
604 static int method_set_deployment(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
605 return set_machine_info(userdata, bus, m, PROP_DEPLOYMENT, method_set_deployment, error);
608 static int method_set_location(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
609 return set_machine_info(userdata, bus, m, PROP_LOCATION, method_set_location, error);
612 static const sd_bus_vtable hostname_vtable[] = {
613 SD_BUS_VTABLE_START(0),
614 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
615 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
616 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
617 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
618 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
619 SD_BUS_PROPERTY("Deployment", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_DEPLOYMENT, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
620 SD_BUS_PROPERTY("Location", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_LOCATION, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
621 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
622 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
623 SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
624 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
625 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
626 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
627 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
628 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
629 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
630 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
631 SD_BUS_METHOD("SetDeployment", "sb", NULL, method_set_deployment, SD_BUS_VTABLE_UNPRIVILEGED),
632 SD_BUS_METHOD("SetLocation", "sb", NULL, method_set_location, SD_BUS_VTABLE_UNPRIVILEGED),
636 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
637 _cleanup_bus_unref_ sd_bus *bus = NULL;
644 r = sd_bus_default_system(&bus);
646 log_error("Failed to get system bus connection: %s", strerror(-r));
650 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
652 log_error("Failed to register object: %s", strerror(-r));
656 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
658 log_error("Failed to register name: %s", strerror(-r));
662 r = sd_bus_attach_event(bus, event, 0);
664 log_error("Failed to attach bus to event loop: %s", strerror(-r));
674 int main(int argc, char *argv[]) {
675 Context context = {};
677 _cleanup_event_unref_ sd_event *event = NULL;
678 _cleanup_bus_unref_ sd_bus *bus = NULL;
681 log_set_target(LOG_TARGET_AUTO);
682 log_parse_environment();
689 log_error("This program takes no arguments.");
695 log_error("This program takes no arguments.");
700 r = sd_event_default(&event);
702 log_error("Failed to allocate event loop: %s", strerror(-r));
706 sd_event_set_watchdog(event, true);
708 r = connect_bus(&context, event, &bus);
712 r = context_read_data(&context);
714 log_error("Failed to read hostname and machine information: %s", strerror(-r));
718 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
720 log_error("Failed to run event loop: %s", strerror(-r));
725 context_free(&context, bus);
727 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;