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"
46 typedef struct Context {
47 char *data[_PROP_MAX];
48 Hashmap *polkit_registry;
51 static void context_reset(Context *c) {
56 for (p = 0; p < _PROP_MAX; p++) {
62 static void context_free(Context *c, sd_bus *bus) {
66 bus_verify_polkit_async_registry_free(bus, c->polkit_registry);
69 static int context_read_data(Context *c) {
76 c->data[PROP_HOSTNAME] = gethostname_malloc();
77 if (!c->data[PROP_HOSTNAME])
80 r = read_one_line_file("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]);
81 if (r < 0 && r != -ENOENT)
84 r = parse_env_file("/etc/machine-info", NEWLINE,
85 "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME],
86 "ICON_NAME", &c->data[PROP_ICON_NAME],
87 "CHASSIS", &c->data[PROP_CHASSIS],
89 if (r < 0 && r != -ENOENT)
95 static bool check_nss(void) {
98 dl = dlopen("libnss_myhostname.so.2", RTLD_LAZY);
107 static bool valid_chassis(const char *chassis) {
111 return nulstr_contains(
122 static const char* fallback_chassis(void) {
128 v = detect_virtualization(NULL);
130 if (v == VIRTUALIZATION_VM)
132 if (v == VIRTUALIZATION_CONTAINER)
135 r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type);
139 r = safe_atou(type, &t);
144 /* We only list the really obvious cases here as the ACPI data
145 * is not really super reliable.
147 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
149 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
172 r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type);
176 r = safe_atou(type, &t);
181 /* We only list the really obvious cases here. The DMI data is
182 unreliable enough, so let's not do any additional guesswork
185 See the SMBIOS Specification 2.7.1 section 7.4.1 for
186 details about the values listed here:
188 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
216 static char* context_fallback_icon_name(Context *c) {
221 if (!isempty(c->data[PROP_CHASSIS]))
222 return strappend("computer-", c->data[PROP_CHASSIS]);
224 chassis = fallback_chassis();
226 return strappend("computer-", chassis);
228 return strdup("computer");
231 static int context_write_data_hostname(Context *c) {
236 if (isempty(c->data[PROP_HOSTNAME]))
239 hn = c->data[PROP_HOSTNAME];
241 if (sethostname(hn, strlen(hn)) < 0)
247 static int context_write_data_static_hostname(Context *c) {
251 if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
253 if (unlink("/etc/hostname") < 0)
254 return errno == ENOENT ? 0 : -errno;
258 return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]);
261 static int context_write_data_other(Context *c) {
263 static const char * const name[_PROP_MAX] = {
264 [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME",
265 [PROP_ICON_NAME] = "ICON_NAME",
266 [PROP_CHASSIS] = "CHASSIS"
274 r = load_env_file("/etc/machine-info", NULL, &l);
275 if (r < 0 && r != -ENOENT)
278 for (p = 2; p < _PROP_MAX; p++) {
283 if (isempty(c->data[p])) {
284 strv_env_unset(l, name[p]);
288 if (asprintf(&t, "%s=%s", name[p], strempty(c->data[p])) < 0) {
293 u = strv_env_set(l, t);
302 if (strv_isempty(l)) {
304 if (unlink("/etc/machine-info") < 0)
305 return errno == ENOENT ? 0 : -errno;
310 r = write_env_file_label("/etc/machine-info", l);
316 static int property_get_icon_name(
319 const char *interface,
320 const char *property,
321 sd_bus_message *reply,
325 _cleanup_free_ char *n = NULL;
326 Context *c = userdata;
330 if (isempty(c->data[PROP_ICON_NAME]))
331 name = n = context_fallback_icon_name(c);
333 name = c->data[PROP_ICON_NAME];
338 r = 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,
354 Context *c = userdata;
358 if (isempty(c->data[PROP_CHASSIS]))
359 name = fallback_chassis();
361 name = c->data[PROP_CHASSIS];
363 r = sd_bus_message_append(reply, "s", name);
370 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
371 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
372 Context *c = userdata;
378 r = sd_bus_message_read(m, "sb", &name, &interactive);
380 return sd_bus_reply_method_errno(bus, m, r, NULL);
383 name = c->data[PROP_STATIC_HOSTNAME];
388 if (!hostname_is_valid(name))
389 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
391 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
392 return sd_bus_reply_method_return(bus, m, NULL);
394 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, &error, method_set_hostname, c);
396 return sd_bus_reply_method_errno(bus, m, r, &error);
398 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
404 free(c->data[PROP_HOSTNAME]);
405 c->data[PROP_HOSTNAME] = h;
407 r = context_write_data_hostname(c);
409 log_error("Failed to set host name: %s", strerror(-r));
410 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set hostname: %s", strerror(-r));
413 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
415 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
417 return sd_bus_reply_method_return(bus, m, NULL);
420 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
421 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
422 Context *c = userdata;
427 r = sd_bus_message_read(m, "sb", &name, &interactive);
429 return sd_bus_reply_method_errno(bus, m, r, NULL);
434 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
435 return sd_bus_reply_method_return(bus, m, NULL);
437 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, &error, method_set_static_hostname, c);
439 return sd_bus_reply_method_errno(bus, m, r, &error);
441 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
444 free(c->data[PROP_STATIC_HOSTNAME]);
445 c->data[PROP_STATIC_HOSTNAME] = NULL;
449 if (!hostname_is_valid(name))
450 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
456 free(c->data[PROP_STATIC_HOSTNAME]);
457 c->data[PROP_STATIC_HOSTNAME] = h;
460 r = context_write_data_static_hostname(c);
462 log_error("Failed to write static host name: %s", strerror(-r));
463 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set static hostname: %s", strerror(-r));
466 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
468 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
470 return sd_bus_reply_method_return(bus, m, NULL);
473 static int set_machine_info(Context *c, sd_bus *bus, sd_bus_message *m, int prop, sd_bus_message_handler_t cb) {
474 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
483 r = sd_bus_message_read(m, "sb", &name, &interactive);
485 return sd_bus_reply_method_errno(bus, m, r, NULL);
490 if (streq_ptr(name, c->data[prop]))
491 return sd_bus_reply_method_return(bus, m, NULL);
493 /* Since the pretty hostname should always be changed at the
494 * same time as the static one, use the same policy action for
497 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
498 "org.freedesktop.hostname1.set-static-hostname" :
499 "org.freedesktop.hostname1.set-machine-info", interactive, &error, cb, c);
501 return sd_bus_reply_method_errno(bus, m, r, &error);
503 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
507 c->data[prop] = NULL;
511 /* The icon name might ultimately be used as file
512 * name, so better be safe than sorry */
514 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
515 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
516 if (prop == PROP_PRETTY_HOSTNAME &&
517 (string_has_cc(name) || chars_intersect(name, "\t")))
518 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
519 if (prop == PROP_CHASSIS && !valid_chassis(name))
520 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
530 r = context_write_data_other(c);
532 log_error("Failed to write machine info: %s", strerror(-r));
533 return sd_bus_reply_method_errnof(bus, m, r, "Failed to write machine info: %s", strerror(-r));
536 log_info("Changed %s to '%s'",
537 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
538 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
540 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
541 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
542 prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
544 return sd_bus_reply_method_return(bus, m, NULL);
547 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
548 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname);
551 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata) {
552 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name);
555 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata) {
556 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis);
559 static const sd_bus_vtable hostname_vtable[] = {
560 SD_BUS_VTABLE_START(0),
561 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
562 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
563 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
564 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
565 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
566 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, 0),
567 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, 0),
568 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, 0),
569 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, 0),
570 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, 0),
574 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
575 _cleanup_bus_unref_ sd_bus *bus = NULL;
582 r = sd_bus_open_system(&bus);
584 log_error("Failed to get system bus connection: %s", strerror(-r));
588 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
590 log_error("Failed to register object: %s", strerror(-r));
594 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", SD_BUS_NAME_DO_NOT_QUEUE);
596 log_error("Failed to register name: %s", strerror(-r));
600 if (r != SD_BUS_NAME_PRIMARY_OWNER) {
601 log_error("Failed to acquire name.");
605 r = sd_bus_attach_event(bus, event, 0);
607 log_error("Failed to attach bus to event loop: %s", strerror(-r));
617 int main(int argc, char *argv[]) {
618 Context context = {};
620 _cleanup_event_unref_ sd_event *event = NULL;
621 _cleanup_bus_unref_ sd_bus *bus = NULL;
624 log_set_target(LOG_TARGET_AUTO);
625 log_parse_environment();
632 log_error("This program takes no arguments.");
638 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
641 log_error("This program takes no arguments.");
646 r = sd_event_new(&event);
648 log_error("Failed to allocate event loop: %s", strerror(-r));
652 r = connect_bus(&context, event, &bus);
656 r = context_read_data(&context);
658 log_error("Failed to read timezone data: %s", strerror(-r));
662 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC);
664 log_error("Failed to run event loop: %s", strerror(-r));
671 context_free(&context, bus);
673 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;