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"
269 _cleanup_strv_free_ char **l = NULL;
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)
291 u = strv_env_set(l, t);
301 if (strv_isempty(l)) {
303 if (unlink("/etc/machine-info") < 0)
304 return errno == ENOENT ? 0 : -errno;
309 return write_env_file_label("/etc/machine-info", l);
312 static int property_get_icon_name(
315 const char *interface,
316 const char *property,
317 sd_bus_message *reply,
321 _cleanup_free_ char *n = NULL;
322 Context *c = userdata;
326 if (isempty(c->data[PROP_ICON_NAME]))
327 name = n = context_fallback_icon_name(c);
329 name = c->data[PROP_ICON_NAME];
334 r = sd_bus_message_append(reply, "s", name);
341 static int property_get_chassis(
344 const char *interface,
345 const char *property,
346 sd_bus_message *reply,
350 Context *c = userdata;
354 if (isempty(c->data[PROP_CHASSIS]))
355 name = fallback_chassis();
357 name = c->data[PROP_CHASSIS];
359 r = sd_bus_message_append(reply, "s", name);
366 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
367 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
368 Context *c = userdata;
374 r = sd_bus_message_read(m, "sb", &name, &interactive);
376 return sd_bus_reply_method_errno(m, r, NULL);
379 name = c->data[PROP_STATIC_HOSTNAME];
384 if (!hostname_is_valid(name))
385 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
387 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
388 return sd_bus_reply_method_return(m, NULL);
390 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, &error, method_set_hostname, c);
392 return sd_bus_reply_method_errno(m, r, &error);
394 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
400 free(c->data[PROP_HOSTNAME]);
401 c->data[PROP_HOSTNAME] = h;
403 r = context_write_data_hostname(c);
405 log_error("Failed to set host name: %s", strerror(-r));
406 return sd_bus_reply_method_errnof(m, r, "Failed to set hostname: %s", strerror(-r));
409 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
411 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
413 return sd_bus_reply_method_return(m, NULL);
416 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
417 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
418 Context *c = userdata;
423 r = sd_bus_message_read(m, "sb", &name, &interactive);
425 return sd_bus_reply_method_errno(m, r, NULL);
430 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
431 return sd_bus_reply_method_return(m, NULL);
433 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, &error, method_set_static_hostname, c);
435 return sd_bus_reply_method_errno(m, r, &error);
437 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
440 free(c->data[PROP_STATIC_HOSTNAME]);
441 c->data[PROP_STATIC_HOSTNAME] = NULL;
445 if (!hostname_is_valid(name))
446 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
452 free(c->data[PROP_STATIC_HOSTNAME]);
453 c->data[PROP_STATIC_HOSTNAME] = h;
456 r = context_write_data_static_hostname(c);
458 log_error("Failed to write static host name: %s", strerror(-r));
459 return sd_bus_reply_method_errnof(m, r, "Failed to set static hostname: %s", strerror(-r));
462 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
464 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
466 return sd_bus_reply_method_return(m, NULL);
469 static int set_machine_info(Context *c, sd_bus *bus, sd_bus_message *m, int prop, sd_bus_message_handler_t cb) {
470 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
479 r = sd_bus_message_read(m, "sb", &name, &interactive);
481 return sd_bus_reply_method_errno(m, r, NULL);
486 if (streq_ptr(name, c->data[prop]))
487 return sd_bus_reply_method_return(m, NULL);
489 /* Since the pretty hostname should always be changed at the
490 * same time as the static one, use the same policy action for
493 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
494 "org.freedesktop.hostname1.set-static-hostname" :
495 "org.freedesktop.hostname1.set-machine-info", interactive, &error, cb, c);
497 return sd_bus_reply_method_errno(m, r, &error);
499 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
503 c->data[prop] = NULL;
507 /* The icon name might ultimately be used as file
508 * name, so better be safe than sorry */
510 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
511 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
512 if (prop == PROP_PRETTY_HOSTNAME &&
513 (string_has_cc(name) || chars_intersect(name, "\t")))
514 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
515 if (prop == PROP_CHASSIS && !valid_chassis(name))
516 return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
526 r = context_write_data_other(c);
528 log_error("Failed to write machine info: %s", strerror(-r));
529 return sd_bus_reply_method_errnof(m, r, "Failed to write machine info: %s", strerror(-r));
532 log_info("Changed %s to '%s'",
533 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
534 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
536 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
537 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
538 prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
540 return sd_bus_reply_method_return(m, NULL);
543 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
544 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname);
547 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata) {
548 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name);
551 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata) {
552 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis);
555 static const sd_bus_vtable hostname_vtable[] = {
556 SD_BUS_VTABLE_START(0),
557 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
558 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
559 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
560 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
561 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
562 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, 0),
563 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, 0),
564 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, 0),
565 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, 0),
566 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, 0),
570 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
571 _cleanup_bus_unref_ sd_bus *bus = NULL;
578 r = sd_bus_default_system(&bus);
580 log_error("Failed to get system bus connection: %s", strerror(-r));
584 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
586 log_error("Failed to register object: %s", strerror(-r));
590 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", SD_BUS_NAME_DO_NOT_QUEUE);
592 log_error("Failed to register name: %s", strerror(-r));
596 if (r != SD_BUS_NAME_PRIMARY_OWNER) {
597 log_error("Failed to acquire name.");
601 r = sd_bus_attach_event(bus, event, 0);
603 log_error("Failed to attach bus to event loop: %s", strerror(-r));
613 int main(int argc, char *argv[]) {
614 Context context = {};
616 _cleanup_event_unref_ sd_event *event = NULL;
617 _cleanup_bus_unref_ sd_bus *bus = NULL;
620 log_set_target(LOG_TARGET_AUTO);
621 log_parse_environment();
628 log_error("This program takes no arguments.");
634 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
637 log_error("This program takes no arguments.");
642 r = sd_event_default(&event);
644 log_error("Failed to allocate event loop: %s", strerror(-r));
648 r = connect_bus(&context, event, &bus);
652 r = context_read_data(&context);
654 log_error("Failed to read timezone data: %s", strerror(-r));
658 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC);
660 log_error("Failed to run event loop: %s", strerror(-r));
667 context_free(&context, bus);
669 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;