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>
32 #include "fileio-label.h"
34 #include "event-util.h"
35 #include "selinux-util.h"
36 #include "hostname-util.h"
38 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
56 typedef struct Context
{
57 char *data
[_PROP_MAX
];
58 Hashmap
*polkit_registry
;
61 static void context_reset(Context
*c
) {
66 for (p
= 0; p
< _PROP_MAX
; p
++)
67 c
->data
[p
] = mfree(c
->data
[p
]);
70 static void context_free(Context
*c
) {
74 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
77 static int context_read_data(Context
*c
) {
85 assert_se(uname(&u
) >= 0);
86 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
87 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
88 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
89 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
90 !c
->data
[PROP_KERNEL_VERSION
])
93 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
94 if (!c
->data
[PROP_HOSTNAME
])
97 r
= read_hostname_config("/etc/hostname", &c
->data
[PROP_STATIC_HOSTNAME
]);
98 if (r
< 0 && r
!= -ENOENT
)
101 r
= parse_env_file("/etc/machine-info", NEWLINE
,
102 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
103 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
104 "CHASSIS", &c
->data
[PROP_CHASSIS
],
105 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
106 "LOCATION", &c
->data
[PROP_LOCATION
],
108 if (r
< 0 && r
!= -ENOENT
)
111 r
= parse_env_file("/etc/os-release", NEWLINE
,
112 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
113 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
116 r
= parse_env_file("/usr/lib/os-release", NEWLINE
,
117 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
118 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
121 if (r
< 0 && r
!= -ENOENT
)
127 static bool valid_chassis(const char *chassis
) {
130 return nulstr_contains(
143 static bool valid_deployment(const char *deployment
) {
146 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
149 static const char* fallback_chassis(void) {
155 v
= detect_virtualization();
157 if (VIRTUALIZATION_IS_VM(v
))
159 if (VIRTUALIZATION_IS_CONTAINER(v
))
162 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
166 r
= safe_atou(type
, &t
);
171 /* We only list the really obvious cases here as the ACPI data
172 * is not really super reliable.
174 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
176 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
199 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
203 r
= safe_atou(type
, &t
);
208 /* We only list the really obvious cases here. The DMI data is
209 unreliable enough, so let's not do any additional guesswork
212 See the SMBIOS Specification 2.7.1 section 7.4.1 for
213 details about the values listed here:
215 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
243 static char* context_fallback_icon_name(Context
*c
) {
248 if (!isempty(c
->data
[PROP_CHASSIS
]))
249 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
251 chassis
= fallback_chassis();
253 return strappend("computer-", chassis
);
255 return strdup("computer");
259 static bool hostname_is_useful(const char *hn
) {
260 return !isempty(hn
) && !is_localhost(hn
);
263 static int context_update_kernel_hostname(Context
*c
) {
264 const char *static_hn
;
269 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
271 /* /etc/hostname with something other than "localhost"
272 * has the highest preference ... */
273 if (hostname_is_useful(static_hn
))
276 /* ... the transient host name, (ie: DHCP) comes next ... */
277 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
278 hn
= c
->data
[PROP_HOSTNAME
];
280 /* ... fallback to static "localhost.*" ignored above ... */
281 else if (!isempty(static_hn
))
284 /* ... and the ultimate fallback */
288 if (sethostname_idempotent(hn
) < 0)
294 static int context_write_data_static_hostname(Context
*c
) {
298 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
300 if (unlink("/etc/hostname") < 0)
301 return errno
== ENOENT
? 0 : -errno
;
305 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
308 static int context_write_data_machine_info(Context
*c
) {
310 static const char * const name
[_PROP_MAX
] = {
311 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
312 [PROP_ICON_NAME
] = "ICON_NAME",
313 [PROP_CHASSIS
] = "CHASSIS",
314 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
315 [PROP_LOCATION
] = "LOCATION",
318 _cleanup_strv_free_
char **l
= NULL
;
323 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
324 if (r
< 0 && r
!= -ENOENT
)
327 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
328 _cleanup_free_
char *t
= NULL
;
333 if (isempty(c
->data
[p
])) {
334 strv_env_unset(l
, name
[p
]);
338 t
= strjoin(name
[p
], "=", c
->data
[p
], NULL
);
342 u
= strv_env_set(l
, t
);
350 if (strv_isempty(l
)) {
351 if (unlink("/etc/machine-info") < 0)
352 return errno
== ENOENT
? 0 : -errno
;
357 return write_env_file_label("/etc/machine-info", l
);
360 static int property_get_icon_name(
363 const char *interface
,
364 const char *property
,
365 sd_bus_message
*reply
,
367 sd_bus_error
*error
) {
369 _cleanup_free_
char *n
= NULL
;
370 Context
*c
= userdata
;
373 if (isempty(c
->data
[PROP_ICON_NAME
]))
374 name
= n
= context_fallback_icon_name(c
);
376 name
= c
->data
[PROP_ICON_NAME
];
381 return sd_bus_message_append(reply
, "s", name
);
384 static int property_get_chassis(
387 const char *interface
,
388 const char *property
,
389 sd_bus_message
*reply
,
391 sd_bus_error
*error
) {
393 Context
*c
= userdata
;
396 if (isempty(c
->data
[PROP_CHASSIS
]))
397 name
= fallback_chassis();
399 name
= c
->data
[PROP_CHASSIS
];
401 return sd_bus_message_append(reply
, "s", name
);
404 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
405 Context
*c
= userdata
;
414 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
419 name
= c
->data
[PROP_STATIC_HOSTNAME
];
424 if (!hostname_is_valid(name
, false))
425 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
427 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
428 return sd_bus_reply_method_return(m
, NULL
);
430 r
= bus_verify_polkit_async(
433 "org.freedesktop.hostname1.set-hostname",
442 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
448 free(c
->data
[PROP_HOSTNAME
]);
449 c
->data
[PROP_HOSTNAME
] = h
;
451 r
= context_update_kernel_hostname(c
);
453 log_error_errno(r
, "Failed to set host name: %m");
454 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
457 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
459 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
461 return sd_bus_reply_method_return(m
, NULL
);
464 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
465 Context
*c
= userdata
;
473 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
480 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
481 return sd_bus_reply_method_return(m
, NULL
);
483 r
= bus_verify_polkit_async(
486 "org.freedesktop.hostname1.set-static-hostname",
495 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
498 c
->data
[PROP_STATIC_HOSTNAME
] = mfree(c
->data
[PROP_STATIC_HOSTNAME
]);
502 if (!hostname_is_valid(name
, false))
503 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
509 free(c
->data
[PROP_STATIC_HOSTNAME
]);
510 c
->data
[PROP_STATIC_HOSTNAME
] = h
;
513 r
= context_update_kernel_hostname(c
);
515 log_error_errno(r
, "Failed to set host name: %m");
516 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
519 r
= context_write_data_static_hostname(c
);
521 log_error_errno(r
, "Failed to write static host name: %m");
522 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %s", strerror(-r
));
525 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
527 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
529 return sd_bus_reply_method_return(m
, NULL
);
532 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
540 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
547 if (streq_ptr(name
, c
->data
[prop
]))
548 return sd_bus_reply_method_return(m
, NULL
);
550 /* Since the pretty hostname should always be changed at the
551 * same time as the static one, use the same policy action for
554 r
= bus_verify_polkit_async(
557 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
566 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
569 c
->data
[prop
] = mfree(c
->data
[prop
]);
573 /* The icon name might ultimately be used as file
574 * name, so better be safe than sorry */
576 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
577 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
578 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
579 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
580 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
581 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
582 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
583 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
584 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
585 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
595 r
= context_write_data_machine_info(c
);
597 log_error_errno(r
, "Failed to write machine info: %m");
598 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %s", strerror(-r
));
601 log_info("Changed %s to '%s'",
602 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
603 prop
== PROP_DEPLOYMENT
? "deployment" :
604 prop
== PROP_LOCATION
? "location" :
605 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
607 (void) sd_bus_emit_properties_changed(
608 sd_bus_message_get_bus(m
),
609 "/org/freedesktop/hostname1",
610 "org.freedesktop.hostname1",
611 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
612 prop
== PROP_DEPLOYMENT
? "Deployment" :
613 prop
== PROP_LOCATION
? "Location" :
614 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
616 return sd_bus_reply_method_return(m
, NULL
);
619 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
620 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
623 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
624 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
627 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
628 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
631 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
632 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
635 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
636 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
639 static const sd_bus_vtable hostname_vtable
[] = {
640 SD_BUS_VTABLE_START(0),
641 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
642 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
643 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
644 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
645 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
646 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
647 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
648 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
649 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
650 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
651 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
652 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
653 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
654 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
655 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
656 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
657 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
658 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
659 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
663 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
664 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
671 r
= sd_bus_default_system(&bus
);
673 return log_error_errno(r
, "Failed to get system bus connection: %m");
675 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
677 return log_error_errno(r
, "Failed to register object: %m");
679 r
= sd_bus_request_name(bus
, "org.freedesktop.hostname1", 0);
681 return log_error_errno(r
, "Failed to register name: %m");
683 r
= sd_bus_attach_event(bus
, event
, 0);
685 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
693 int main(int argc
, char *argv
[]) {
694 Context context
= {};
695 _cleanup_event_unref_ sd_event
*event
= NULL
;
696 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
699 log_set_target(LOG_TARGET_AUTO
);
700 log_parse_environment();
704 mac_selinux_init("/etc");
707 log_error("This program takes no arguments.");
712 r
= sd_event_default(&event
);
714 log_error_errno(r
, "Failed to allocate event loop: %m");
718 sd_event_set_watchdog(event
, true);
720 r
= connect_bus(&context
, event
, &bus
);
724 r
= context_read_data(&context
);
726 log_error_errno(r
, "Failed to read hostname and machine information: %m");
730 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
732 log_error_errno(r
, "Failed to run event loop: %m");
737 context_free(&context
);
739 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;