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/>.
24 #include <sys/utsname.h>
27 #include "alloc-util.h"
31 #include "fileio-label.h"
32 #include "hostname-util.h"
33 #include "parse-util.h"
34 #include "path-util.h"
35 #include "selinux-util.h"
37 #include "user-util.h"
41 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
59 typedef struct Context
{
60 char *data
[_PROP_MAX
];
61 Hashmap
*polkit_registry
;
64 static void context_reset(Context
*c
) {
69 for (p
= 0; p
< _PROP_MAX
; p
++)
70 c
->data
[p
] = mfree(c
->data
[p
]);
73 static void context_free(Context
*c
) {
77 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
80 static int context_read_data(Context
*c
) {
88 assert_se(uname(&u
) >= 0);
89 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
90 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
91 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
92 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
93 !c
->data
[PROP_KERNEL_VERSION
])
96 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
97 if (!c
->data
[PROP_HOSTNAME
])
100 r
= read_hostname_config("/etc/hostname", &c
->data
[PROP_STATIC_HOSTNAME
]);
101 if (r
< 0 && r
!= -ENOENT
)
104 r
= parse_env_file("/etc/machine-info", NEWLINE
,
105 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
106 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
107 "CHASSIS", &c
->data
[PROP_CHASSIS
],
108 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
109 "LOCATION", &c
->data
[PROP_LOCATION
],
111 if (r
< 0 && r
!= -ENOENT
)
114 r
= parse_env_file("/etc/os-release", NEWLINE
,
115 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
116 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
119 r
= parse_env_file("/usr/lib/os-release", NEWLINE
,
120 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
121 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
124 if (r
< 0 && r
!= -ENOENT
)
130 static bool valid_chassis(const char *chassis
) {
133 return nulstr_contains(
146 static bool valid_deployment(const char *deployment
) {
149 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
152 static const char* fallback_chassis(void) {
158 v
= detect_virtualization();
160 if (VIRTUALIZATION_IS_VM(v
))
162 if (VIRTUALIZATION_IS_CONTAINER(v
))
165 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
169 r
= safe_atou(type
, &t
);
174 /* We only list the really obvious cases here as the ACPI data
175 * is not really super reliable.
177 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
179 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
202 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
206 r
= safe_atou(type
, &t
);
211 /* We only list the really obvious cases here. The DMI data is
212 unreliable enough, so let's not do any additional guesswork
215 See the SMBIOS Specification 3.0 section 7.4.1 for
216 details about the values listed here:
218 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
250 static char* context_fallback_icon_name(Context
*c
) {
255 if (!isempty(c
->data
[PROP_CHASSIS
]))
256 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
258 chassis
= fallback_chassis();
260 return strappend("computer-", chassis
);
262 return strdup("computer");
266 static bool hostname_is_useful(const char *hn
) {
267 return !isempty(hn
) && !is_localhost(hn
);
270 static int context_update_kernel_hostname(Context
*c
) {
271 const char *static_hn
;
276 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
278 /* /etc/hostname with something other than "localhost"
279 * has the highest preference ... */
280 if (hostname_is_useful(static_hn
))
283 /* ... the transient host name, (ie: DHCP) comes next ... */
284 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
285 hn
= c
->data
[PROP_HOSTNAME
];
287 /* ... fallback to static "localhost.*" ignored above ... */
288 else if (!isempty(static_hn
))
291 /* ... and the ultimate fallback */
295 if (sethostname_idempotent(hn
) < 0)
301 static int context_write_data_static_hostname(Context
*c
) {
305 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
307 if (unlink("/etc/hostname") < 0)
308 return errno
== ENOENT
? 0 : -errno
;
312 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
315 static int context_write_data_machine_info(Context
*c
) {
317 static const char * const name
[_PROP_MAX
] = {
318 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
319 [PROP_ICON_NAME
] = "ICON_NAME",
320 [PROP_CHASSIS
] = "CHASSIS",
321 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
322 [PROP_LOCATION
] = "LOCATION",
325 _cleanup_strv_free_
char **l
= NULL
;
330 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
331 if (r
< 0 && r
!= -ENOENT
)
334 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
335 _cleanup_free_
char *t
= NULL
;
340 if (isempty(c
->data
[p
])) {
341 strv_env_unset(l
, name
[p
]);
345 t
= strjoin(name
[p
], "=", c
->data
[p
], NULL
);
349 u
= strv_env_set(l
, t
);
357 if (strv_isempty(l
)) {
358 if (unlink("/etc/machine-info") < 0)
359 return errno
== ENOENT
? 0 : -errno
;
364 return write_env_file_label("/etc/machine-info", l
);
367 static int property_get_icon_name(
370 const char *interface
,
371 const char *property
,
372 sd_bus_message
*reply
,
374 sd_bus_error
*error
) {
376 _cleanup_free_
char *n
= NULL
;
377 Context
*c
= userdata
;
380 if (isempty(c
->data
[PROP_ICON_NAME
]))
381 name
= n
= context_fallback_icon_name(c
);
383 name
= c
->data
[PROP_ICON_NAME
];
388 return sd_bus_message_append(reply
, "s", name
);
391 static int property_get_chassis(
394 const char *interface
,
395 const char *property
,
396 sd_bus_message
*reply
,
398 sd_bus_error
*error
) {
400 Context
*c
= userdata
;
403 if (isempty(c
->data
[PROP_CHASSIS
]))
404 name
= fallback_chassis();
406 name
= c
->data
[PROP_CHASSIS
];
408 return sd_bus_message_append(reply
, "s", name
);
411 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
412 Context
*c
= userdata
;
421 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
426 name
= c
->data
[PROP_STATIC_HOSTNAME
];
431 if (!hostname_is_valid(name
, false))
432 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
434 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
435 return sd_bus_reply_method_return(m
, NULL
);
437 r
= bus_verify_polkit_async(
440 "org.freedesktop.hostname1.set-hostname",
449 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
455 free(c
->data
[PROP_HOSTNAME
]);
456 c
->data
[PROP_HOSTNAME
] = h
;
458 r
= context_update_kernel_hostname(c
);
460 log_error_errno(r
, "Failed to set host name: %m");
461 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
464 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
466 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
468 return sd_bus_reply_method_return(m
, NULL
);
471 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
472 Context
*c
= userdata
;
480 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
487 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
488 return sd_bus_reply_method_return(m
, NULL
);
490 r
= bus_verify_polkit_async(
493 "org.freedesktop.hostname1.set-static-hostname",
502 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
505 c
->data
[PROP_STATIC_HOSTNAME
] = mfree(c
->data
[PROP_STATIC_HOSTNAME
]);
509 if (!hostname_is_valid(name
, false))
510 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
516 free(c
->data
[PROP_STATIC_HOSTNAME
]);
517 c
->data
[PROP_STATIC_HOSTNAME
] = h
;
520 r
= context_update_kernel_hostname(c
);
522 log_error_errno(r
, "Failed to set host name: %m");
523 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
526 r
= context_write_data_static_hostname(c
);
528 log_error_errno(r
, "Failed to write static host name: %m");
529 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %s", strerror(-r
));
532 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
534 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
536 return sd_bus_reply_method_return(m
, NULL
);
539 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
547 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
554 if (streq_ptr(name
, c
->data
[prop
]))
555 return sd_bus_reply_method_return(m
, NULL
);
557 /* Since the pretty hostname should always be changed at the
558 * same time as the static one, use the same policy action for
561 r
= bus_verify_polkit_async(
564 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
573 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
576 c
->data
[prop
] = mfree(c
->data
[prop
]);
580 /* The icon name might ultimately be used as file
581 * name, so better be safe than sorry */
583 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
584 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
585 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
586 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
587 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
588 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
589 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
590 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
591 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
592 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
602 r
= context_write_data_machine_info(c
);
604 log_error_errno(r
, "Failed to write machine info: %m");
605 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %s", strerror(-r
));
608 log_info("Changed %s to '%s'",
609 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
610 prop
== PROP_DEPLOYMENT
? "deployment" :
611 prop
== PROP_LOCATION
? "location" :
612 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
614 (void) sd_bus_emit_properties_changed(
615 sd_bus_message_get_bus(m
),
616 "/org/freedesktop/hostname1",
617 "org.freedesktop.hostname1",
618 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
619 prop
== PROP_DEPLOYMENT
? "Deployment" :
620 prop
== PROP_LOCATION
? "Location" :
621 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
623 return sd_bus_reply_method_return(m
, NULL
);
626 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
627 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
630 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
631 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
634 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
635 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
638 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
639 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
642 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
643 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
646 static const sd_bus_vtable hostname_vtable
[] = {
647 SD_BUS_VTABLE_START(0),
648 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
649 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
650 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
651 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
652 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
653 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
654 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
655 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
656 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
657 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
658 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
659 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
660 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
661 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
662 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
663 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
664 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
665 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
666 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
670 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
671 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
678 r
= sd_bus_default_system(&bus
);
680 return log_error_errno(r
, "Failed to get system bus connection: %m");
682 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
684 return log_error_errno(r
, "Failed to register object: %m");
686 r
= sd_bus_request_name(bus
, "org.freedesktop.hostname1", 0);
688 return log_error_errno(r
, "Failed to register name: %m");
690 r
= sd_bus_attach_event(bus
, event
, 0);
692 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
700 int main(int argc
, char *argv
[]) {
701 Context context
= {};
702 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
703 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
706 log_set_target(LOG_TARGET_AUTO
);
707 log_parse_environment();
711 mac_selinux_init("/etc");
714 log_error("This program takes no arguments.");
719 r
= sd_event_default(&event
);
721 log_error_errno(r
, "Failed to allocate event loop: %m");
725 sd_event_set_watchdog(event
, true);
727 r
= connect_bus(&context
, event
, &bus
);
731 r
= context_read_data(&context
);
733 log_error_errno(r
, "Failed to read hostname and machine information: %m");
737 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
739 log_error_errno(r
, "Failed to run event loop: %m");
744 context_free(&context
);
746 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;