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 "-.:")
54 typedef struct Context {
55 char *data[_PROP_MAX];
56 Hashmap *polkit_registry;
59 static void context_reset(Context *c) {
64 for (p = 0; p < _PROP_MAX; p++) {
70 static void context_free(Context *c, sd_bus *bus) {
74 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
77 static int context_read_data(Context *c) {
85 assert_se(uname(&u) >= 0);
86 c->data[PROP_KERNEL_NAME] = strdup(u.sysname);
87 c->data[PROP_KERNEL_RELEASE] = strdup(u.release);
88 c->data[PROP_KERNEL_VERSION] = strdup(u.version);
89 if (!c->data[PROP_KERNEL_NAME] || !c->data[PROP_KERNEL_RELEASE] ||
90 !c->data[PROP_KERNEL_VERSION])
93 c->data[PROP_HOSTNAME] = gethostname_malloc();
94 if (!c->data[PROP_HOSTNAME])
97 r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
98 if (r < 0 && r != -ENOENT)
101 r = parse_env_file("/etc/machine-info", NEWLINE,
102 "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
103 "ICON_NAME", &c->data[PROP_ICON_NAME],
104 "CHASSIS", &c->data[PROP_CHASSIS],
105 "DEPLOYMENT", &c->data[PROP_DEPLOYMENT],
107 if (r < 0 && r != -ENOENT)
110 r = parse_env_file("/etc/os-release", NEWLINE,
111 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
112 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
115 r = parse_env_file("/usr/lib/os-release", NEWLINE,
116 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
117 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
121 if (r < 0 && r != -ENOENT)
127 static bool valid_chassis(const char *chassis) {
130 return nulstr_contains(
142 static bool valid_deployment(const char *deployment) {
145 return in_charset(deployment, VALID_DEPLOYMENT_CHARS);
148 static const char* fallback_chassis(void) {
154 v = detect_virtualization(NULL);
156 if (v == VIRTUALIZATION_VM)
158 if (v == VIRTUALIZATION_CONTAINER)
161 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
165 r = safe_atou(type, &t);
170 /* We only list the really obvious cases here as the ACPI data
171 * is not really super reliable.
173 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
175 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
198 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
202 r = safe_atou(type, &t);
207 /* We only list the really obvious cases here. The DMI data is
208 unreliable enough, so let's not do any additional guesswork
211 See the SMBIOS Specification 2.7.1 section 7.4.1 for
212 details about the values listed here:
214 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
242 static char* context_fallback_icon_name(Context *c) {
247 if (!isempty(c->data[PROP_CHASSIS]))
248 return strappend("computer-", c->data[PROP_CHASSIS]);
250 chassis = fallback_chassis();
252 return strappend("computer-", chassis);
254 return strdup("computer");
258 static bool hostname_is_useful(const char *hn) {
259 return !isempty(hn) && !is_localhost(hn);
262 static int context_update_kernel_hostname(Context *c) {
263 const char *static_hn;
268 static_hn = c->data[PROP_STATIC_HOSTNAME];
270 /* /etc/hostname with something other than "localhost"
271 * has the highest preference ... */
272 if (hostname_is_useful(static_hn))
275 /* ... the transient host name, (ie: DHCP) comes next ...*/
276 else if (!isempty(c->data[PROP_HOSTNAME]))
277 hn = c->data[PROP_HOSTNAME];
279 /* ... fallback to static "localhost.*" ignored above ... */
280 else if (!isempty(static_hn))
283 /* ... and the ultimate fallback */
287 if (sethostname(hn, strlen(hn)) < 0)
293 static int context_write_data_static_hostname(Context *c) {
297 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
299 if (unlink("/etc/hostname") < 0)
300 return errno == ENOENT ? 0 : -errno;
304 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
307 static int context_write_data_machine_info(Context *c) {
309 static const char * const name[_PROP_MAX] = {
310 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
311 [PROP_ICON_NAME] = "ICON_NAME",
312 [PROP_CHASSIS] = "CHASSIS",
313 [PROP_DEPLOYMENT] = "DEPLOYMENT",
316 _cleanup_strv_free_ char **l = NULL;
321 r = load_env_file(NULL, "/etc/machine-info", NULL, &l);
322 if (r < 0 && r != -ENOENT)
325 for (p = PROP_PRETTY_HOSTNAME; p <= PROP_DEPLOYMENT; p++) {
326 _cleanup_free_ char *t = NULL;
331 if (isempty(c->data[p])) {
332 strv_env_unset(l, name[p]);
336 t = strjoin(name[p], "=", c->data[p], NULL);
340 u = strv_env_set(l, t);
348 if (strv_isempty(l)) {
349 if (unlink("/etc/machine-info") < 0)
350 return errno == ENOENT ? 0 : -errno;
355 return write_env_file_label("/etc/machine-info", l);
358 static int property_get_icon_name(
361 const char *interface,
362 const char *property,
363 sd_bus_message *reply,
365 sd_bus_error *error) {
367 _cleanup_free_ char *n = NULL;
368 Context *c = userdata;
371 if (isempty(c->data[PROP_ICON_NAME]))
372 name = n = context_fallback_icon_name(c);
374 name = c->data[PROP_ICON_NAME];
379 return sd_bus_message_append(reply, "s", name);
382 static int property_get_chassis(
385 const char *interface,
386 const char *property,
387 sd_bus_message *reply,
389 sd_bus_error *error) {
391 Context *c = userdata;
394 if (isempty(c->data[PROP_CHASSIS]))
395 name = fallback_chassis();
397 name = c->data[PROP_CHASSIS];
399 return sd_bus_message_append(reply, "s", name);
402 static int property_get_deployment(
405 const char *interface,
406 const char *property,
407 sd_bus_message *reply,
409 sd_bus_error *error) {
411 Context *c = userdata;
414 name = c->data[PROP_DEPLOYMENT];
416 return sd_bus_message_append(reply, "s", name);
419 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
420 Context *c = userdata;
426 r = sd_bus_message_read(m, "sb", &name, &interactive);
431 name = c->data[PROP_STATIC_HOSTNAME];
436 if (!hostname_is_valid(name))
437 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
439 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
440 return sd_bus_reply_method_return(m, NULL);
442 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, error, method_set_hostname, c);
446 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
452 free(c->data[PROP_HOSTNAME]);
453 c->data[PROP_HOSTNAME] = h;
455 r = context_update_kernel_hostname(c);
457 log_error("Failed to set host name: %s", strerror(-r));
458 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
461 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
463 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
465 return sd_bus_reply_method_return(m, NULL);
468 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
469 Context *c = userdata;
474 r = sd_bus_message_read(m, "sb", &name, &interactive);
481 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
482 return sd_bus_reply_method_return(m, NULL);
484 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, error, method_set_static_hostname, c);
488 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
491 free(c->data[PROP_STATIC_HOSTNAME]);
492 c->data[PROP_STATIC_HOSTNAME] = NULL;
496 if (!hostname_is_valid(name))
497 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
503 free(c->data[PROP_STATIC_HOSTNAME]);
504 c->data[PROP_STATIC_HOSTNAME] = h;
507 r = context_update_kernel_hostname(c);
509 log_error("Failed to set host name: %s", strerror(-r));
510 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
513 r = context_write_data_static_hostname(c);
515 log_error("Failed to write static host name: %s", strerror(-r));
516 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
519 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
521 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
523 return sd_bus_reply_method_return(m, NULL);
526 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) {
535 r = sd_bus_message_read(m, "sb", &name, &interactive);
542 if (streq_ptr(name, c->data[prop]))
543 return sd_bus_reply_method_return(m, NULL);
545 /* Since the pretty hostname should always be changed at the
546 * same time as the static one, use the same policy action for
549 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
550 "org.freedesktop.hostname1.set-static-hostname" :
551 "org.freedesktop.hostname1.set-machine-info", interactive, error, cb, c);
555 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
559 c->data[prop] = NULL;
563 /* The icon name might ultimately be used as file
564 * name, so better be safe than sorry */
566 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
567 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
568 if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL))
569 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
570 if (prop == PROP_CHASSIS && !valid_chassis(name))
571 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
572 if (prop == PROP_DEPLOYMENT && !valid_deployment(name))
573 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name);
583 r = context_write_data_machine_info(c);
585 log_error("Failed to write machine info: %s", strerror(-r));
586 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
589 log_info("Changed %s to '%s'",
590 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
591 prop == PROP_DEPLOYMENT ? "deployment" :
592 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
594 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
595 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
596 prop == PROP_DEPLOYMENT ? "Deployment" :
597 prop == PROP_CHASSIS ? "Chassis" : "IconName" , NULL);
599 return sd_bus_reply_method_return(m, NULL);
602 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
603 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
606 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
607 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
610 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
611 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
614 static int method_set_deployment(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
615 return set_machine_info(userdata, bus, m, PROP_DEPLOYMENT, method_set_deployment, error);
618 static const sd_bus_vtable hostname_vtable[] = {
619 SD_BUS_VTABLE_START(0),
620 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
621 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
622 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
623 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
624 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
625 SD_BUS_PROPERTY("Deployment", "s", property_get_deployment, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
626 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
627 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
628 SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
629 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
630 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
631 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
632 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
633 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
634 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
635 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
636 SD_BUS_METHOD("SetDeployment", "sb", NULL, method_set_deployment, SD_BUS_VTABLE_UNPRIVILEGED),
640 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
641 _cleanup_bus_unref_ sd_bus *bus = NULL;
648 r = sd_bus_default_system(&bus);
650 log_error("Failed to get system bus connection: %s", strerror(-r));
654 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
656 log_error("Failed to register object: %s", strerror(-r));
660 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
662 log_error("Failed to register name: %s", strerror(-r));
666 r = sd_bus_attach_event(bus, event, 0);
668 log_error("Failed to attach bus to event loop: %s", strerror(-r));
678 int main(int argc, char *argv[]) {
679 Context context = {};
681 _cleanup_event_unref_ sd_event *event = NULL;
682 _cleanup_bus_unref_ sd_bus *bus = NULL;
685 log_set_target(LOG_TARGET_AUTO);
686 log_parse_environment();
693 log_error("This program takes no arguments.");
699 log_error("This program takes no arguments.");
704 r = sd_event_default(&event);
706 log_error("Failed to allocate event loop: %s", strerror(-r));
710 sd_event_set_watchdog(event, true);
712 r = connect_bus(&context, event, &bus);
716 r = context_read_data(&context);
718 log_error("Failed to read hostname and machine information: %s", strerror(-r));
722 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
724 log_error("Failed to run event loop: %s", strerror(-r));
729 context_free(&context, bus);
731 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;