2 This file is part of systemd.
4 Copyright 2011 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <sys/utsname.h>
25 #include "alloc-util.h"
29 #include "fileio-label.h"
30 #include "hostname-util.h"
31 #include "parse-util.h"
32 #include "path-util.h"
33 #include "selinux-util.h"
35 #include "user-util.h"
39 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
57 typedef struct Context
{
58 char *data
[_PROP_MAX
];
59 Hashmap
*polkit_registry
;
62 static void context_reset(Context
*c
) {
67 for (p
= 0; p
< _PROP_MAX
; p
++)
68 c
->data
[p
] = mfree(c
->data
[p
]);
71 static void context_free(Context
*c
) {
75 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
78 static int context_read_data(Context
*c
) {
86 assert_se(uname(&u
) >= 0);
87 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
88 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
89 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
90 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
91 !c
->data
[PROP_KERNEL_VERSION
])
94 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
95 if (!c
->data
[PROP_HOSTNAME
])
98 r
= read_hostname_config("/etc/hostname", &c
->data
[PROP_STATIC_HOSTNAME
]);
99 if (r
< 0 && r
!= -ENOENT
)
102 r
= parse_env_file("/etc/machine-info", NEWLINE
,
103 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
104 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
105 "CHASSIS", &c
->data
[PROP_CHASSIS
],
106 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
107 "LOCATION", &c
->data
[PROP_LOCATION
],
109 if (r
< 0 && r
!= -ENOENT
)
112 r
= parse_env_file("/etc/os-release", NEWLINE
,
113 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
114 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
117 r
= parse_env_file("/usr/lib/os-release", NEWLINE
,
118 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
119 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
122 if (r
< 0 && r
!= -ENOENT
)
128 static bool valid_chassis(const char *chassis
) {
131 return nulstr_contains(
144 static bool valid_deployment(const char *deployment
) {
147 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
150 static const char* fallback_chassis(void) {
156 v
= detect_virtualization();
158 if (VIRTUALIZATION_IS_VM(v
))
160 if (VIRTUALIZATION_IS_CONTAINER(v
))
163 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
167 r
= safe_atou(type
, &t
);
172 /* We only list the really obvious cases here as the ACPI data
173 * is not really super reliable.
175 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
177 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
200 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
204 r
= safe_atou(type
, &t
);
209 /* We only list the really obvious cases here. The DMI data is
210 unreliable enough, so let's not do any additional guesswork
213 See the SMBIOS Specification 3.0 section 7.4.1 for
214 details about the values listed here:
216 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
248 static char* context_fallback_icon_name(Context
*c
) {
253 if (!isempty(c
->data
[PROP_CHASSIS
]))
254 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
256 chassis
= fallback_chassis();
258 return strappend("computer-", chassis
);
260 return strdup("computer");
264 static bool hostname_is_useful(const char *hn
) {
265 return !isempty(hn
) && !is_localhost(hn
);
268 static int context_update_kernel_hostname(Context
*c
) {
269 const char *static_hn
;
274 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
276 /* /etc/hostname with something other than "localhost"
277 * has the highest preference ... */
278 if (hostname_is_useful(static_hn
))
281 /* ... the transient host name, (ie: DHCP) comes next ... */
282 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
283 hn
= c
->data
[PROP_HOSTNAME
];
285 /* ... fallback to static "localhost.*" ignored above ... */
286 else if (!isempty(static_hn
))
289 /* ... and the ultimate fallback */
293 if (sethostname_idempotent(hn
) < 0)
299 static int context_write_data_static_hostname(Context
*c
) {
303 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
305 if (unlink("/etc/hostname") < 0)
306 return errno
== ENOENT
? 0 : -errno
;
310 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
313 static int context_write_data_machine_info(Context
*c
) {
315 static const char * const name
[_PROP_MAX
] = {
316 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
317 [PROP_ICON_NAME
] = "ICON_NAME",
318 [PROP_CHASSIS
] = "CHASSIS",
319 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
320 [PROP_LOCATION
] = "LOCATION",
323 _cleanup_strv_free_
char **l
= NULL
;
328 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
329 if (r
< 0 && r
!= -ENOENT
)
332 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
333 _cleanup_free_
char *t
= NULL
;
338 if (isempty(c
->data
[p
])) {
339 strv_env_unset(l
, name
[p
]);
343 t
= strjoin(name
[p
], "=", c
->data
[p
], NULL
);
347 u
= strv_env_set(l
, t
);
355 if (strv_isempty(l
)) {
356 if (unlink("/etc/machine-info") < 0)
357 return errno
== ENOENT
? 0 : -errno
;
362 return write_env_file_label("/etc/machine-info", l
);
365 static int property_get_icon_name(
368 const char *interface
,
369 const char *property
,
370 sd_bus_message
*reply
,
372 sd_bus_error
*error
) {
374 _cleanup_free_
char *n
= NULL
;
375 Context
*c
= userdata
;
378 if (isempty(c
->data
[PROP_ICON_NAME
]))
379 name
= n
= context_fallback_icon_name(c
);
381 name
= c
->data
[PROP_ICON_NAME
];
386 return sd_bus_message_append(reply
, "s", name
);
389 static int property_get_chassis(
392 const char *interface
,
393 const char *property
,
394 sd_bus_message
*reply
,
396 sd_bus_error
*error
) {
398 Context
*c
= userdata
;
401 if (isempty(c
->data
[PROP_CHASSIS
]))
402 name
= fallback_chassis();
404 name
= c
->data
[PROP_CHASSIS
];
406 return sd_bus_message_append(reply
, "s", name
);
409 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
410 Context
*c
= userdata
;
419 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
424 name
= c
->data
[PROP_STATIC_HOSTNAME
];
429 if (!hostname_is_valid(name
, false))
430 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
432 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
433 return sd_bus_reply_method_return(m
, NULL
);
435 r
= bus_verify_polkit_async(
438 "org.freedesktop.hostname1.set-hostname",
447 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
453 free(c
->data
[PROP_HOSTNAME
]);
454 c
->data
[PROP_HOSTNAME
] = h
;
456 r
= context_update_kernel_hostname(c
);
458 log_error_errno(r
, "Failed to set host name: %m");
459 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
462 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
464 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
466 return sd_bus_reply_method_return(m
, NULL
);
469 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
470 Context
*c
= userdata
;
478 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
485 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
486 return sd_bus_reply_method_return(m
, NULL
);
488 r
= bus_verify_polkit_async(
491 "org.freedesktop.hostname1.set-static-hostname",
500 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
503 c
->data
[PROP_STATIC_HOSTNAME
] = mfree(c
->data
[PROP_STATIC_HOSTNAME
]);
507 if (!hostname_is_valid(name
, false))
508 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
514 free(c
->data
[PROP_STATIC_HOSTNAME
]);
515 c
->data
[PROP_STATIC_HOSTNAME
] = h
;
518 r
= context_update_kernel_hostname(c
);
520 log_error_errno(r
, "Failed to set host name: %m");
521 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
524 r
= context_write_data_static_hostname(c
);
526 log_error_errno(r
, "Failed to write static host name: %m");
527 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %s", strerror(-r
));
530 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
532 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
534 return sd_bus_reply_method_return(m
, NULL
);
537 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
545 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
552 if (streq_ptr(name
, c
->data
[prop
]))
553 return sd_bus_reply_method_return(m
, NULL
);
555 /* Since the pretty hostname should always be changed at the
556 * same time as the static one, use the same policy action for
559 r
= bus_verify_polkit_async(
562 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
571 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
574 c
->data
[prop
] = mfree(c
->data
[prop
]);
578 /* The icon name might ultimately be used as file
579 * name, so better be safe than sorry */
581 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
582 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
583 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
584 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
585 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
586 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
587 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
588 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
589 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
590 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
600 r
= context_write_data_machine_info(c
);
602 log_error_errno(r
, "Failed to write machine info: %m");
603 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %s", strerror(-r
));
606 log_info("Changed %s to '%s'",
607 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
608 prop
== PROP_DEPLOYMENT
? "deployment" :
609 prop
== PROP_LOCATION
? "location" :
610 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
612 (void) sd_bus_emit_properties_changed(
613 sd_bus_message_get_bus(m
),
614 "/org/freedesktop/hostname1",
615 "org.freedesktop.hostname1",
616 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
617 prop
== PROP_DEPLOYMENT
? "Deployment" :
618 prop
== PROP_LOCATION
? "Location" :
619 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
621 return sd_bus_reply_method_return(m
, NULL
);
624 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
625 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
628 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
629 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
632 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
633 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
636 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
637 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
640 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
641 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
644 static const sd_bus_vtable hostname_vtable
[] = {
645 SD_BUS_VTABLE_START(0),
646 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
647 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
648 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
649 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
650 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
651 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
652 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
653 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
654 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
655 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
656 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
657 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
658 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
659 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
660 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
661 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
662 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
663 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
664 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
668 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
669 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
676 r
= sd_bus_default_system(&bus
);
678 return log_error_errno(r
, "Failed to get system bus connection: %m");
680 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
682 return log_error_errno(r
, "Failed to register object: %m");
684 r
= sd_bus_request_name(bus
, "org.freedesktop.hostname1", 0);
686 return log_error_errno(r
, "Failed to register name: %m");
688 r
= sd_bus_attach_event(bus
, event
, 0);
690 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
698 int main(int argc
, char *argv
[]) {
699 Context context
= {};
700 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
701 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
704 log_set_target(LOG_TARGET_AUTO
);
705 log_parse_environment();
709 mac_selinux_init("/etc");
712 log_error("This program takes no arguments.");
717 r
= sd_event_default(&event
);
719 log_error_errno(r
, "Failed to allocate event loop: %m");
723 sd_event_set_watchdog(event
, true);
725 r
= connect_bus(&context
, event
, &bus
);
729 r
= context_read_data(&context
);
731 log_error_errno(r
, "Failed to read hostname and machine information: %m");
735 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
737 log_error_errno(r
, "Failed to run event loop: %m");
742 context_free(&context
);
744 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;