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>
27 #include "alloc-util.h"
31 #include "event-util.h"
32 #include "fileio-label.h"
33 #include "hostname-util.h"
34 #include "parse-util.h"
35 #include "path-util.h"
36 #include "selinux-util.h"
38 #include "user-util.h"
42 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
60 typedef struct Context
{
61 char *data
[_PROP_MAX
];
62 Hashmap
*polkit_registry
;
65 static void context_reset(Context
*c
) {
70 for (p
= 0; p
< _PROP_MAX
; p
++)
71 c
->data
[p
] = mfree(c
->data
[p
]);
74 static void context_free(Context
*c
) {
78 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
81 static int context_read_data(Context
*c
) {
89 assert_se(uname(&u
) >= 0);
90 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
91 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
92 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
93 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
94 !c
->data
[PROP_KERNEL_VERSION
])
97 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
98 if (!c
->data
[PROP_HOSTNAME
])
101 r
= read_hostname_config("/etc/hostname", &c
->data
[PROP_STATIC_HOSTNAME
]);
102 if (r
< 0 && r
!= -ENOENT
)
105 r
= parse_env_file("/etc/machine-info", NEWLINE
,
106 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
107 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
108 "CHASSIS", &c
->data
[PROP_CHASSIS
],
109 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
110 "LOCATION", &c
->data
[PROP_LOCATION
],
112 if (r
< 0 && r
!= -ENOENT
)
115 r
= parse_env_file("/etc/os-release", NEWLINE
,
116 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
117 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
120 r
= parse_env_file("/usr/lib/os-release", NEWLINE
,
121 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
122 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
125 if (r
< 0 && r
!= -ENOENT
)
131 static bool valid_chassis(const char *chassis
) {
134 return nulstr_contains(
147 static bool valid_deployment(const char *deployment
) {
150 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
153 static const char* fallback_chassis(void) {
159 v
= detect_virtualization();
161 if (VIRTUALIZATION_IS_VM(v
))
163 if (VIRTUALIZATION_IS_CONTAINER(v
))
166 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
170 r
= safe_atou(type
, &t
);
175 /* We only list the really obvious cases here as the ACPI data
176 * is not really super reliable.
178 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
180 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
203 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
207 r
= safe_atou(type
, &t
);
212 /* We only list the really obvious cases here. The DMI data is
213 unreliable enough, so let's not do any additional guesswork
216 See the SMBIOS Specification 2.7.1 section 7.4.1 for
217 details about the values listed here:
219 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
247 static char* context_fallback_icon_name(Context
*c
) {
252 if (!isempty(c
->data
[PROP_CHASSIS
]))
253 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
255 chassis
= fallback_chassis();
257 return strappend("computer-", chassis
);
259 return strdup("computer");
263 static bool hostname_is_useful(const char *hn
) {
264 return !isempty(hn
) && !is_localhost(hn
);
267 static int context_update_kernel_hostname(Context
*c
) {
268 const char *static_hn
;
273 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
275 /* /etc/hostname with something other than "localhost"
276 * has the highest preference ... */
277 if (hostname_is_useful(static_hn
))
280 /* ... the transient host name, (ie: DHCP) comes next ... */
281 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
282 hn
= c
->data
[PROP_HOSTNAME
];
284 /* ... fallback to static "localhost.*" ignored above ... */
285 else if (!isempty(static_hn
))
288 /* ... and the ultimate fallback */
292 if (sethostname_idempotent(hn
) < 0)
298 static int context_write_data_static_hostname(Context
*c
) {
302 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
304 if (unlink("/etc/hostname") < 0)
305 return errno
== ENOENT
? 0 : -errno
;
309 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
312 static int context_write_data_machine_info(Context
*c
) {
314 static const char * const name
[_PROP_MAX
] = {
315 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
316 [PROP_ICON_NAME
] = "ICON_NAME",
317 [PROP_CHASSIS
] = "CHASSIS",
318 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
319 [PROP_LOCATION
] = "LOCATION",
322 _cleanup_strv_free_
char **l
= NULL
;
327 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
328 if (r
< 0 && r
!= -ENOENT
)
331 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
332 _cleanup_free_
char *t
= NULL
;
337 if (isempty(c
->data
[p
])) {
338 strv_env_unset(l
, name
[p
]);
342 t
= strjoin(name
[p
], "=", c
->data
[p
], NULL
);
346 u
= strv_env_set(l
, t
);
354 if (strv_isempty(l
)) {
355 if (unlink("/etc/machine-info") < 0)
356 return errno
== ENOENT
? 0 : -errno
;
361 return write_env_file_label("/etc/machine-info", l
);
364 static int property_get_icon_name(
367 const char *interface
,
368 const char *property
,
369 sd_bus_message
*reply
,
371 sd_bus_error
*error
) {
373 _cleanup_free_
char *n
= NULL
;
374 Context
*c
= userdata
;
377 if (isempty(c
->data
[PROP_ICON_NAME
]))
378 name
= n
= context_fallback_icon_name(c
);
380 name
= c
->data
[PROP_ICON_NAME
];
385 return sd_bus_message_append(reply
, "s", name
);
388 static int property_get_chassis(
391 const char *interface
,
392 const char *property
,
393 sd_bus_message
*reply
,
395 sd_bus_error
*error
) {
397 Context
*c
= userdata
;
400 if (isempty(c
->data
[PROP_CHASSIS
]))
401 name
= fallback_chassis();
403 name
= c
->data
[PROP_CHASSIS
];
405 return sd_bus_message_append(reply
, "s", name
);
408 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
409 Context
*c
= userdata
;
418 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
423 name
= c
->data
[PROP_STATIC_HOSTNAME
];
428 if (!hostname_is_valid(name
, false))
429 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
431 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
432 return sd_bus_reply_method_return(m
, NULL
);
434 r
= bus_verify_polkit_async(
437 "org.freedesktop.hostname1.set-hostname",
446 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
452 free(c
->data
[PROP_HOSTNAME
]);
453 c
->data
[PROP_HOSTNAME
] = h
;
455 r
= context_update_kernel_hostname(c
);
457 log_error_errno(r
, "Failed to set host name: %m");
458 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
461 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
463 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
465 return sd_bus_reply_method_return(m
, NULL
);
468 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
469 Context
*c
= userdata
;
477 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
484 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
485 return sd_bus_reply_method_return(m
, NULL
);
487 r
= bus_verify_polkit_async(
490 "org.freedesktop.hostname1.set-static-hostname",
499 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
502 c
->data
[PROP_STATIC_HOSTNAME
] = mfree(c
->data
[PROP_STATIC_HOSTNAME
]);
506 if (!hostname_is_valid(name
, false))
507 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
513 free(c
->data
[PROP_STATIC_HOSTNAME
]);
514 c
->data
[PROP_STATIC_HOSTNAME
] = h
;
517 r
= context_update_kernel_hostname(c
);
519 log_error_errno(r
, "Failed to set host name: %m");
520 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
523 r
= context_write_data_static_hostname(c
);
525 log_error_errno(r
, "Failed to write static host name: %m");
526 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %s", strerror(-r
));
529 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
531 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
533 return sd_bus_reply_method_return(m
, NULL
);
536 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
544 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
551 if (streq_ptr(name
, c
->data
[prop
]))
552 return sd_bus_reply_method_return(m
, NULL
);
554 /* Since the pretty hostname should always be changed at the
555 * same time as the static one, use the same policy action for
558 r
= bus_verify_polkit_async(
561 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
570 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
573 c
->data
[prop
] = mfree(c
->data
[prop
]);
577 /* The icon name might ultimately be used as file
578 * name, so better be safe than sorry */
580 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
581 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
582 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
583 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
584 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
585 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
586 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
587 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
588 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
589 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
599 r
= context_write_data_machine_info(c
);
601 log_error_errno(r
, "Failed to write machine info: %m");
602 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %s", strerror(-r
));
605 log_info("Changed %s to '%s'",
606 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
607 prop
== PROP_DEPLOYMENT
? "deployment" :
608 prop
== PROP_LOCATION
? "location" :
609 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
611 (void) sd_bus_emit_properties_changed(
612 sd_bus_message_get_bus(m
),
613 "/org/freedesktop/hostname1",
614 "org.freedesktop.hostname1",
615 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
616 prop
== PROP_DEPLOYMENT
? "Deployment" :
617 prop
== PROP_LOCATION
? "Location" :
618 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
620 return sd_bus_reply_method_return(m
, NULL
);
623 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
624 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
627 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
628 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
631 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
632 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
635 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
636 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
639 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
640 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
643 static const sd_bus_vtable hostname_vtable
[] = {
644 SD_BUS_VTABLE_START(0),
645 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
646 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
647 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
648 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
649 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
650 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
651 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
652 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
653 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
654 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
655 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
656 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
657 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
658 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
659 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
660 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
661 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
662 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
663 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
667 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
668 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
675 r
= sd_bus_default_system(&bus
);
677 return log_error_errno(r
, "Failed to get system bus connection: %m");
679 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
681 return log_error_errno(r
, "Failed to register object: %m");
683 r
= sd_bus_request_name(bus
, "org.freedesktop.hostname1", 0);
685 return log_error_errno(r
, "Failed to register name: %m");
687 r
= sd_bus_attach_event(bus
, event
, 0);
689 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
697 int main(int argc
, char *argv
[]) {
698 Context context
= {};
699 _cleanup_event_unref_ sd_event
*event
= NULL
;
700 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
703 log_set_target(LOG_TARGET_AUTO
);
704 log_parse_environment();
708 mac_selinux_init("/etc");
711 log_error("This program takes no arguments.");
716 r
= sd_event_default(&event
);
718 log_error_errno(r
, "Failed to allocate event loop: %m");
722 sd_event_set_watchdog(event
, true);
724 r
= connect_bus(&context
, event
, &bus
);
728 r
= context_read_data(&context
);
730 log_error_errno(r
, "Failed to read hostname and machine information: %m");
734 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
736 log_error_errno(r
, "Failed to run event loop: %m");
741 context_free(&context
);
743 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;