1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 #include <sys/utsname.h>
26 #include "alloc-util.h"
30 #include "fileio-label.h"
31 #include "hostname-util.h"
32 #include "parse-util.h"
33 #include "path-util.h"
34 #include "selinux-util.h"
36 #include "user-util.h"
40 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
58 typedef struct Context
{
59 char *data
[_PROP_MAX
];
60 Hashmap
*polkit_registry
;
63 static void context_reset(Context
*c
) {
68 for (p
= 0; p
< _PROP_MAX
; p
++)
69 c
->data
[p
] = mfree(c
->data
[p
]);
72 static void context_free(Context
*c
) {
76 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
79 static int context_read_data(Context
*c
) {
87 assert_se(uname(&u
) >= 0);
88 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
89 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
90 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
91 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
92 !c
->data
[PROP_KERNEL_VERSION
])
95 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
96 if (!c
->data
[PROP_HOSTNAME
])
99 r
= read_etc_hostname(NULL
, &c
->data
[PROP_STATIC_HOSTNAME
]);
100 if (r
< 0 && r
!= -ENOENT
)
103 r
= parse_env_file("/etc/machine-info", NEWLINE
,
104 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
105 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
106 "CHASSIS", &c
->data
[PROP_CHASSIS
],
107 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
108 "LOCATION", &c
->data
[PROP_LOCATION
],
110 if (r
< 0 && r
!= -ENOENT
)
113 r
= parse_env_file("/etc/os-release", NEWLINE
,
114 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
115 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
118 r
= parse_env_file("/usr/lib/os-release", NEWLINE
,
119 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
120 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
123 if (r
< 0 && r
!= -ENOENT
)
129 static bool valid_chassis(const char *chassis
) {
132 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) {
157 v
= detect_virtualization();
158 if (VIRTUALIZATION_IS_VM(v
))
160 if (VIRTUALIZATION_IS_CONTAINER(v
))
163 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
167 r
= safe_atou(type
, &t
);
172 /* We only list the really obvious cases here. The DMI data is unreliable enough, so let's not do any
173 additional guesswork on top of that.
175 See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here:
177 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
182 case 0x3: /* Desktop */
183 case 0x4: /* Low Profile Desktop */
184 case 0x6: /* Mini Tower */
185 case 0x7: /* Tower */
188 case 0x8: /* Portable */
189 case 0x9: /* Laptop */
190 case 0xA: /* Notebook */
191 case 0xE: /* Sub Notebook */
194 case 0xB: /* Hand Held */
197 case 0x11: /* Main Server Chassis */
198 case 0x1C: /* Blade */
199 case 0x1D: /* Blade Enclosure */
202 case 0x1E: /* Tablet */
205 case 0x1F: /* Convertible */
206 case 0x20: /* Detachable */
207 return "convertible";
211 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
215 r
= safe_atou(type
, &t
);
220 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
222 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
224 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
229 case 1: /* Desktop */
230 case 3: /* Workstation */
231 case 6: /* Appliance PC */
237 case 4: /* Enterprise Server */
238 case 5: /* SOHO Server */
239 case 7: /* Performance Server */
249 static char* context_fallback_icon_name(Context
*c
) {
254 if (!isempty(c
->data
[PROP_CHASSIS
]))
255 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
257 chassis
= fallback_chassis();
259 return strappend("computer-", chassis
);
261 return strdup("computer");
265 static bool hostname_is_useful(const char *hn
) {
266 return !isempty(hn
) && !is_localhost(hn
);
269 static int context_update_kernel_hostname(Context
*c
) {
270 const char *static_hn
;
275 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
277 /* /etc/hostname with something other than "localhost"
278 * has the highest preference ... */
279 if (hostname_is_useful(static_hn
))
282 /* ... the transient host name, (ie: DHCP) comes next ... */
283 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
284 hn
= c
->data
[PROP_HOSTNAME
];
286 /* ... fallback to static "localhost.*" ignored above ... */
287 else if (!isempty(static_hn
))
290 /* ... and the ultimate fallback */
292 hn
= FALLBACK_HOSTNAME
;
294 if (sethostname_idempotent(hn
) < 0)
300 static int context_write_data_static_hostname(Context
*c
) {
304 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
306 if (unlink("/etc/hostname") < 0)
307 return errno
== ENOENT
? 0 : -errno
;
311 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
314 static int context_write_data_machine_info(Context
*c
) {
316 static const char * const name
[_PROP_MAX
] = {
317 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
318 [PROP_ICON_NAME
] = "ICON_NAME",
319 [PROP_CHASSIS
] = "CHASSIS",
320 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
321 [PROP_LOCATION
] = "LOCATION",
324 _cleanup_strv_free_
char **l
= NULL
;
329 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
330 if (r
< 0 && r
!= -ENOENT
)
333 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
334 _cleanup_free_
char *t
= NULL
;
339 if (isempty(c
->data
[p
])) {
340 strv_env_unset(l
, name
[p
]);
344 t
= strjoin(name
[p
], "=", c
->data
[p
]);
348 u
= strv_env_set(l
, t
);
356 if (strv_isempty(l
)) {
357 if (unlink("/etc/machine-info") < 0)
358 return errno
== ENOENT
? 0 : -errno
;
363 return write_env_file_label("/etc/machine-info", l
);
366 static int property_get_icon_name(
369 const char *interface
,
370 const char *property
,
371 sd_bus_message
*reply
,
373 sd_bus_error
*error
) {
375 _cleanup_free_
char *n
= NULL
;
376 Context
*c
= userdata
;
379 if (isempty(c
->data
[PROP_ICON_NAME
]))
380 name
= n
= context_fallback_icon_name(c
);
382 name
= c
->data
[PROP_ICON_NAME
];
387 return sd_bus_message_append(reply
, "s", name
);
390 static int property_get_chassis(
393 const char *interface
,
394 const char *property
,
395 sd_bus_message
*reply
,
397 sd_bus_error
*error
) {
399 Context
*c
= userdata
;
402 if (isempty(c
->data
[PROP_CHASSIS
]))
403 name
= fallback_chassis();
405 name
= c
->data
[PROP_CHASSIS
];
407 return sd_bus_message_append(reply
, "s", name
);
410 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
411 Context
*c
= userdata
;
420 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
425 name
= c
->data
[PROP_STATIC_HOSTNAME
];
428 name
= FALLBACK_HOSTNAME
;
430 if (!hostname_is_valid(name
, false))
431 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
433 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
434 return sd_bus_reply_method_return(m
, NULL
);
436 r
= bus_verify_polkit_async(
439 "org.freedesktop.hostname1.set-hostname",
448 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
454 free(c
->data
[PROP_HOSTNAME
]);
455 c
->data
[PROP_HOSTNAME
] = h
;
457 r
= context_update_kernel_hostname(c
);
459 log_error_errno(r
, "Failed to set host name: %m");
460 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
463 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
465 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
467 return sd_bus_reply_method_return(m
, NULL
);
470 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
471 Context
*c
= userdata
;
479 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
483 name
= empty_to_null(name
);
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: %m");
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: %m");
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
);
549 name
= empty_to_null(name
);
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: %m");
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_(sd_bus_flush_close_unrefp
) 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_async(bus
, NULL
, "org.freedesktop.hostname1", 0, NULL
, NULL
);
685 return log_error_errno(r
, "Failed to request 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");
691 *_bus
= TAKE_PTR(bus
);
696 int main(int argc
, char *argv
[]) {
697 Context context
= {};
698 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
699 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
702 log_set_target(LOG_TARGET_AUTO
);
703 log_parse_environment();
710 log_error("This program takes no arguments.");
715 r
= sd_event_default(&event
);
717 log_error_errno(r
, "Failed to allocate event loop: %m");
721 sd_event_set_watchdog(event
, true);
723 r
= connect_bus(&context
, event
, &bus
);
727 r
= context_read_data(&context
);
729 log_error_errno(r
, "Failed to read hostname and machine information: %m");
733 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
735 log_error_errno(r
, "Failed to run event loop: %m");
740 context_free(&context
);
742 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;