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];
366 r = sd_bus_message_append(reply, "s", name);
373 static int method_set_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
374 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
375 Context *c = userdata;
377 unsigned interactive;
381 r = sd_bus_message_read(m, "sb", &name, &interactive);
383 return sd_bus_reply_method_errno(bus, m, r, NULL);
386 name = c->data[PROP_STATIC_HOSTNAME];
391 if (!hostname_is_valid(name))
392 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
394 if (streq_ptr(name, c->data[PROP_HOSTNAME]))
395 return sd_bus_reply_method_return(bus, m, NULL);
397 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-hostname", interactive, &error, method_set_hostname, c);
399 return sd_bus_reply_method_errno(bus, m, r, &error);
401 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
407 free(c->data[PROP_HOSTNAME]);
408 c->data[PROP_HOSTNAME] = h;
410 r = context_write_data_hostname(c);
412 log_error("Failed to set host name: %s", strerror(-r));
413 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set hostname: %s", strerror(-r));
416 log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME]));
418 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL);
420 return sd_bus_reply_method_return(bus, m, NULL);
423 static int method_set_static_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
424 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
425 Context *c = userdata;
427 unsigned interactive;
430 r = sd_bus_message_read(m, "sb", &name, &interactive);
432 return sd_bus_reply_method_errno(bus, m, r, NULL);
437 if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
438 return sd_bus_reply_method_return(bus, m, NULL);
440 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, "org.freedesktop.hostname1.set-static-hostname", interactive, &error, method_set_static_hostname, c);
442 return sd_bus_reply_method_errno(bus, m, r, &error);
444 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
447 free(c->data[PROP_STATIC_HOSTNAME]);
448 c->data[PROP_STATIC_HOSTNAME] = NULL;
452 if (!hostname_is_valid(name))
453 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
459 free(c->data[PROP_STATIC_HOSTNAME]);
460 c->data[PROP_STATIC_HOSTNAME] = h;
463 r = context_write_data_static_hostname(c);
465 log_error("Failed to write static host name: %s", strerror(-r));
466 return sd_bus_reply_method_errnof(bus, m, r, "Failed to set static hostname: %s", strerror(-r));
469 log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME]));
471 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL);
473 return sd_bus_reply_method_return(bus, m, NULL);
476 static int set_machine_info(Context *c, sd_bus *bus, sd_bus_message *m, int prop, sd_bus_message_handler_t cb) {
477 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
478 unsigned interactive;
486 r = sd_bus_message_read(m, "sb", &name, &interactive);
488 return sd_bus_reply_method_errno(bus, m, r, NULL);
493 if (streq_ptr(name, c->data[prop]))
494 return sd_bus_reply_method_return(bus, m, NULL);
496 /* Since the pretty hostname should always be changed at the
497 * same time as the static one, use the same policy action for
500 r = bus_verify_polkit_async(bus, &c->polkit_registry, m, prop == PROP_PRETTY_HOSTNAME ?
501 "org.freedesktop.hostname1.set-static-hostname" :
502 "org.freedesktop.hostname1.set-machine-info", interactive, &error, cb, c);
504 return sd_bus_reply_method_errno(bus, m, r, &error);
506 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
510 c->data[prop] = NULL;
514 /* The icon name might ultimately be used as file
515 * name, so better be safe than sorry */
517 if (prop == PROP_ICON_NAME && !filename_is_safe(name))
518 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
519 if (prop == PROP_PRETTY_HOSTNAME &&
520 (string_has_cc(name) || chars_intersect(name, "\t")))
521 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name);
522 if (prop == PROP_CHASSIS && !valid_chassis(name))
523 return sd_bus_reply_method_errorf(bus, m, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
533 r = context_write_data_other(c);
535 log_error("Failed to write machine info: %s", strerror(-r));
536 return sd_bus_reply_method_errnof(bus, m, r, "Failed to write machine info: %s", strerror(-r));
539 log_info("Changed %s to '%s'",
540 prop == PROP_PRETTY_HOSTNAME ? "pretty host name" :
541 prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop]));
543 sd_bus_emit_properties_changed(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
544 prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" :
545 prop == PROP_CHASSIS ? "Chassis" : "IconName", NULL);
547 return sd_bus_reply_method_return(bus, m, NULL);
550 static int method_set_pretty_hostname(sd_bus *bus, sd_bus_message *m, void *userdata) {
551 return set_machine_info(userdata, bus, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname);
554 static int method_set_icon_name(sd_bus *bus, sd_bus_message *m, void *userdata) {
555 return set_machine_info(userdata, bus, m, PROP_ICON_NAME, method_set_icon_name);
558 static int method_set_chassis(sd_bus *bus, sd_bus_message *m, void *userdata) {
559 return set_machine_info(userdata, bus, m, PROP_CHASSIS, method_set_chassis);
562 static const sd_bus_vtable hostname_vtable[] = {
563 SD_BUS_VTABLE_START(0),
564 SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, 0),
565 SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
566 SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
567 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
568 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
569 SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, 0),
570 SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, 0),
571 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, 0),
572 SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, 0),
573 SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, 0),
577 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
578 _cleanup_bus_unref_ sd_bus *bus = NULL;
585 r = sd_bus_open_system(&bus);
587 log_error("Failed to get system bus connection: %s", strerror(-r));
591 r = sd_bus_add_object_vtable(bus, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c);
593 log_error("Failed to register object: %s", strerror(-r));
597 r = sd_bus_request_name(bus, "org.freedesktop.hostname1", SD_BUS_NAME_DO_NOT_QUEUE);
599 log_error("Failed to register name: %s", strerror(-r));
603 if (r != SD_BUS_NAME_PRIMARY_OWNER) {
604 log_error("Failed to acquire name.");
608 r = sd_bus_attach_event(bus, event, 0);
610 log_error("Failed to attach bus to event loop: %s", strerror(-r));
620 int main(int argc, char *argv[]) {
621 Context context = {};
623 _cleanup_event_unref_ sd_event *event = NULL;
624 _cleanup_bus_unref_ sd_bus *bus = NULL;
627 log_set_target(LOG_TARGET_AUTO);
628 log_parse_environment();
635 log_error("This program takes no arguments.");
641 log_warning("Warning: nss-myhostname is not installed. Changing the local hostname might make it unresolveable. Please install nss-myhostname!");
644 log_error("This program takes no arguments.");
649 r = sd_event_new(&event);
651 log_error("Failed to allocate event loop: %s", strerror(-r));
655 r = connect_bus(&context, event, &bus);
659 r = context_read_data(&context);
661 log_error("Failed to read timezone data: %s", strerror(-r));
665 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC);
667 log_error("Failed to run event loop: %s", strerror(-r));
674 context_free(&context, bus);
676 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;