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>
26 #include <sys/capability.h>
33 #include "fileio-label.h"
36 #include "event-util.h"
38 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
56 typedef struct Context {
57 char *data[_PROP_MAX];
58 Hashmap *polkit_registry;
61 static void context_reset(Context *c) {
66 for (p = 0; p < _PROP_MAX; p++) {
72 static void context_free(Context *c) {
76 bus_verify_polkit_async_registry_free(c->polkit_registry);
79 static int context_read_data(Context *c) {
87 assert_se(uname(&u) >= 0);
88 c->data[PROP_KERNEL_NAME] = strdup(u.sysname);
89 c->data[PROP_KERNEL_RELEASE] = strdup(u.release);
90 c->data[PROP_KERNEL_VERSION] = strdup(u.version);
91 if (!c->data[PROP_KERNEL_NAME] || !c->data[PROP_KERNEL_RELEASE] ||
92 !c->data[PROP_KERNEL_VERSION])
95 c->data[PROP_HOSTNAME] = gethostname_malloc();
96 if (!c->data[PROP_HOSTNAME])
99 r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
100 if (r < 0 && r != -ENOENT)
103 r = parse_env_file("/etc/machine-info", NEWLINE,
104 "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
105 "ICON_NAME", &c->data[PROP_ICON_NAME],
106 "CHASSIS", &c->data[PROP_CHASSIS],
107 "DEPLOYMENT", &c->data[PROP_DEPLOYMENT],
108 "LOCATION", &c->data[PROP_LOCATION],
110 if (r < 0 && r != -ENOENT)
113 r = parse_env_file("/etc/os-release", NEWLINE,
114 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
115 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
118 r = parse_env_file("/usr/lib/os-release", NEWLINE,
119 "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME],
120 "CPE_NAME", &c->data[PROP_OS_CPE_NAME],
124 if (r < 0 && r != -ENOENT)
130 static bool valid_chassis(const char *chassis) {
133 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(hn, strlen(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(m, CAP_SYS_ADMIN, "org.freedesktop.hostname1.set-hostname", interactive, &c->polkit_registry, error);
433 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
439 free(c->data[PROP_HOSTNAME]);
440 c->data[PROP_HOSTNAME] = h;
442 r = context_update_kernel_hostname(c);
444 log_error("Failed to set host name: %s", strerror(-r));
445 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
448 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
450 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
452 return sd_bus_reply_method_return(m, NULL);
455 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
456 Context *c = userdata;
461 r = sd_bus_message_read(m, "sb", &name, &interactive);
468 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
469 return sd_bus_reply_method_return(m, NULL);
471 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN, "org.freedesktop.hostname1.set-static-hostname", interactive, &c->polkit_registry, error);
475 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
478 free(c->data[PROP_STATIC_HOSTNAME]);
479 c->data[PROP_STATIC_HOSTNAME] = NULL;
483 if (!hostname_is_valid(name))
484 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
490 free(c->data[PROP_STATIC_HOSTNAME]);
491 c->data[PROP_STATIC_HOSTNAME] = h;
494 r = context_update_kernel_hostname(c);
496 log_error("Failed to set host name: %s", strerror(-r));
497 return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r));
500 r = context_write_data_static_hostname(c);
502 log_error("Failed to write static host name: %s", strerror(-r));
503 return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r));
506 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
508 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
510 return sd_bus_reply_method_return(m, NULL);
513 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) {
522 r = sd_bus_message_read(m, "sb", &name, &interactive);
529 if (streq_ptr(name, c->data[prop]))
530 return sd_bus_reply_method_return(m, NULL);
532 /* Since the pretty hostname should always be changed at the
533 * same time as the static one, use the same policy action for
536 r = bus_verify_polkit_async(m, CAP_SYS_ADMIN,
537 prop == PROP_PRETTY_HOSTNAME ?
538 "org.freedesktop.hostname1.set-static-hostname" :
539 "org.freedesktop.hostname1.set-machine-info", interactive, &c->polkit_registry, error);
543 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
547 c->data[prop] = NULL;
551 /* The icon name might ultimately be used as file
552 * name, so better be safe than sorry */
554 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
555 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
556 if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL))
557 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
558 if (prop == PROP_CHASSIS && !valid_chassis(name))
559 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
560 if (prop == PROP_DEPLOYMENT && !valid_deployment(name))
561 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name);
562 if (prop == PROP_LOCATION && string_has_cc(name, NULL))
563 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid location '%s'", name);
573 r = context_write_data_machine_info(c);
575 log_error("Failed to write machine info: %s", strerror(-r));
576 return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r));
579 log_info("Changed %s to '%s'",
580 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
581 prop == PROP_DEPLOYMENT ? "deployment" :
582 prop == PROP_LOCATION ? "location" :
583 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
585 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
586 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
587 prop == PROP_DEPLOYMENT ? "Deployment" :
588 prop == PROP_LOCATION ? "Location" :
589 prop == PROP_CHASSIS ? "Chassis" : "IconName" , NULL);
591 return sd_bus_reply_method_return(m, NULL);
594 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
595 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error);
598 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
599 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name, error);
602 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
603 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis, error);
606 static int method_set_deployment(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
607 return set_machine_info(userdata, bus, m, PROP_DEPLOYMENT, method_set_deployment, error);
610 static int method_set_location(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
611 return set_machine_info(userdata, bus, m, PROP_LOCATION, method_set_location, error);
614 static const sd_bus_vtable hostname_vtable[] = {
615 SD_BUS_VTABLE_START(0),
616 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
617 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
618 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
619 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
620 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
621 SD_BUS_PROPERTY("Deployment", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_DEPLOYMENT, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
622 SD_BUS_PROPERTY("Location", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_LOCATION, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
623 SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
624 SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST),
625 SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST),
626 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
627 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST),
628 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
629 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
630 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED),
631 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED),
632 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED),
633 SD_BUS_METHOD("SetDeployment", "sb", NULL, method_set_deployment, SD_BUS_VTABLE_UNPRIVILEGED),
634 SD_BUS_METHOD("SetLocation", "sb", NULL, method_set_location, SD_BUS_VTABLE_UNPRIVILEGED),
638 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
639 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
646 r = sd_bus_default_system(&bus);
648 log_error("Failed to get system bus connection: %s", strerror(-r));
652 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
654 log_error("Failed to register object: %s", strerror(-r));
658 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0);
660 log_error("Failed to register name: %s", strerror(-r));
664 r = sd_bus_attach_event(bus, event, 0);
666 log_error("Failed to attach bus to event loop: %s", strerror(-r));
676 int main(int argc, char *argv[]) {
677 Context context = {};
678 _cleanup_event_unref_ sd_event *event = NULL;
679 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
682 log_set_target(LOG_TARGET_AUTO);
683 log_parse_environment();
690 log_error("This program takes no arguments.");
696 log_error("This program takes no arguments.");
701 r = sd_event_default(&event);
703 log_error("Failed to allocate event loop: %s", strerror(-r));
707 sd_event_set_watchdog(event, true);
709 r = connect_bus(&context, event, &bus);
713 r = context_read_data(&context);
715 log_error("Failed to read hostname and machine information: %s", strerror(-r));
719 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL);
721 log_error("Failed to run event loop: %s", strerror(-r));
726 context_free(&context);
728 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;