1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2011 Lennart Poettering
10 #include <sys/utsname.h>
13 #include "alloc-util.h"
17 #include "fileio-label.h"
18 #include "hostname-util.h"
20 #include "parse-util.h"
21 #include "path-util.h"
22 #include "selinux-util.h"
24 #include "user-util.h"
28 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
47 typedef struct Context
{
48 char *data
[_PROP_MAX
];
49 Hashmap
*polkit_registry
;
52 static void context_reset(Context
*c
) {
57 for (p
= 0; p
< _PROP_MAX
; p
++)
58 c
->data
[p
] = mfree(c
->data
[p
]);
61 static void context_free(Context
*c
) {
65 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
68 static int context_read_data(Context
*c
) {
76 assert_se(uname(&u
) >= 0);
77 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
78 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
79 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
80 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
81 !c
->data
[PROP_KERNEL_VERSION
])
84 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
85 if (!c
->data
[PROP_HOSTNAME
])
88 r
= read_etc_hostname(NULL
, &c
->data
[PROP_STATIC_HOSTNAME
]);
89 if (r
< 0 && r
!= -ENOENT
)
92 r
= parse_env_file(NULL
, "/etc/machine-info", NEWLINE
,
93 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
94 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
95 "CHASSIS", &c
->data
[PROP_CHASSIS
],
96 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
97 "LOCATION", &c
->data
[PROP_LOCATION
],
99 if (r
< 0 && r
!= -ENOENT
)
102 r
= parse_os_release(NULL
,
103 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
104 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
105 "HOME_URL", &c
->data
[PROP_HOME_URL
],
107 if (r
< 0 && r
!= -ENOENT
)
113 static bool valid_chassis(const char *chassis
) {
116 return nulstr_contains(
130 static bool valid_deployment(const char *deployment
) {
133 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
136 static const char* fallback_chassis(void) {
141 v
= detect_virtualization();
142 if (VIRTUALIZATION_IS_VM(v
))
144 if (VIRTUALIZATION_IS_CONTAINER(v
))
147 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
151 r
= safe_atou(type
, &t
);
156 /* We only list the really obvious cases here. The DMI data is unreliable enough, so let's not do any
157 additional guesswork on top of that.
159 See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here:
161 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
166 case 0x3: /* Desktop */
167 case 0x4: /* Low Profile Desktop */
168 case 0x6: /* Mini Tower */
169 case 0x7: /* Tower */
172 case 0x8: /* Portable */
173 case 0x9: /* Laptop */
174 case 0xA: /* Notebook */
175 case 0xE: /* Sub Notebook */
178 case 0xB: /* Hand Held */
181 case 0x11: /* Main Server Chassis */
182 case 0x1C: /* Blade */
183 case 0x1D: /* Blade Enclosure */
186 case 0x1E: /* Tablet */
189 case 0x1F: /* Convertible */
190 case 0x20: /* Detachable */
191 return "convertible";
195 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
199 r
= safe_atou(type
, &t
);
204 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
206 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
208 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
213 case 1: /* Desktop */
214 case 3: /* Workstation */
215 case 6: /* Appliance PC */
221 case 4: /* Enterprise Server */
222 case 5: /* SOHO Server */
223 case 7: /* Performance Server */
233 static char* context_fallback_icon_name(Context
*c
) {
238 if (!isempty(c
->data
[PROP_CHASSIS
]))
239 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
241 chassis
= fallback_chassis();
243 return strappend("computer-", chassis
);
245 return strdup("computer");
248 static bool hostname_is_useful(const char *hn
) {
249 return !isempty(hn
) && !is_localhost(hn
);
252 static int context_update_kernel_hostname(Context
*c
) {
253 const char *static_hn
;
258 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
260 /* /etc/hostname with something other than "localhost"
261 * has the highest preference ... */
262 if (hostname_is_useful(static_hn
))
265 /* ... the transient host name, (ie: DHCP) comes next ... */
266 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
267 hn
= c
->data
[PROP_HOSTNAME
];
269 /* ... fallback to static "localhost.*" ignored above ... */
270 else if (!isempty(static_hn
))
273 /* ... and the ultimate fallback */
275 hn
= FALLBACK_HOSTNAME
;
277 if (sethostname_idempotent(hn
) < 0)
283 static int context_write_data_static_hostname(Context
*c
) {
287 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
289 if (unlink("/etc/hostname") < 0)
290 return errno
== ENOENT
? 0 : -errno
;
294 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
297 static int context_write_data_machine_info(Context
*c
) {
299 static const char * const name
[_PROP_MAX
] = {
300 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
301 [PROP_ICON_NAME
] = "ICON_NAME",
302 [PROP_CHASSIS
] = "CHASSIS",
303 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
304 [PROP_LOCATION
] = "LOCATION",
307 _cleanup_strv_free_
char **l
= NULL
;
312 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
313 if (r
< 0 && r
!= -ENOENT
)
316 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
317 _cleanup_free_
char *t
= NULL
;
322 if (isempty(c
->data
[p
])) {
323 strv_env_unset(l
, name
[p
]);
327 t
= strjoin(name
[p
], "=", c
->data
[p
]);
331 u
= strv_env_set(l
, t
);
335 strv_free_and_replace(l
, u
);
338 if (strv_isempty(l
)) {
339 if (unlink("/etc/machine-info") < 0)
340 return errno
== ENOENT
? 0 : -errno
;
345 return write_env_file_label("/etc/machine-info", l
);
348 static int property_get_icon_name(
351 const char *interface
,
352 const char *property
,
353 sd_bus_message
*reply
,
355 sd_bus_error
*error
) {
357 _cleanup_free_
char *n
= NULL
;
358 Context
*c
= userdata
;
361 if (isempty(c
->data
[PROP_ICON_NAME
]))
362 name
= n
= context_fallback_icon_name(c
);
364 name
= c
->data
[PROP_ICON_NAME
];
369 return sd_bus_message_append(reply
, "s", name
);
372 static int property_get_chassis(
375 const char *interface
,
376 const char *property
,
377 sd_bus_message
*reply
,
379 sd_bus_error
*error
) {
381 Context
*c
= userdata
;
384 if (isempty(c
->data
[PROP_CHASSIS
]))
385 name
= fallback_chassis();
387 name
= c
->data
[PROP_CHASSIS
];
389 return sd_bus_message_append(reply
, "s", name
);
392 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
393 Context
*c
= userdata
;
401 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
406 name
= c
->data
[PROP_STATIC_HOSTNAME
];
409 name
= FALLBACK_HOSTNAME
;
411 if (!hostname_is_valid(name
, false))
412 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
414 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
415 return sd_bus_reply_method_return(m
, NULL
);
417 r
= bus_verify_polkit_async(
420 "org.freedesktop.hostname1.set-hostname",
429 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
431 r
= free_and_strdup(&c
->data
[PROP_HOSTNAME
], name
);
435 r
= context_update_kernel_hostname(c
);
437 log_error_errno(r
, "Failed to set host name: %m");
438 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
441 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
443 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
445 return sd_bus_reply_method_return(m
, NULL
);
448 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
449 Context
*c
= userdata
;
457 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
461 name
= empty_to_null(name
);
463 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
464 return sd_bus_reply_method_return(m
, NULL
);
466 if (!isempty(name
) && !hostname_is_valid(name
, false))
467 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
469 r
= bus_verify_polkit_async(
472 "org.freedesktop.hostname1.set-static-hostname",
481 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
483 r
= free_and_strdup(&c
->data
[PROP_STATIC_HOSTNAME
], name
);
487 r
= context_update_kernel_hostname(c
);
489 log_error_errno(r
, "Failed to set host name: %m");
490 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
493 r
= context_write_data_static_hostname(c
);
495 log_error_errno(r
, "Failed to write static host name: %m");
496 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %m");
499 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
501 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
503 return sd_bus_reply_method_return(m
, NULL
);
506 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
514 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
518 name
= empty_to_null(name
);
520 if (streq_ptr(name
, c
->data
[prop
]))
521 return sd_bus_reply_method_return(m
, NULL
);
523 if (!isempty(name
)) {
524 /* The icon name might ultimately be used as file
525 * name, so better be safe than sorry */
527 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
528 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
529 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
530 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
531 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
532 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
533 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
534 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
535 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
536 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
539 /* Since the pretty hostname should always be changed at the
540 * same time as the static one, use the same policy action for
543 r
= bus_verify_polkit_async(
546 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
555 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
557 r
= free_and_strdup(&c
->data
[prop
], name
);
561 r
= context_write_data_machine_info(c
);
563 log_error_errno(r
, "Failed to write machine info: %m");
564 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %m");
567 log_info("Changed %s to '%s'",
568 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
569 prop
== PROP_DEPLOYMENT
? "deployment" :
570 prop
== PROP_LOCATION
? "location" :
571 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
573 (void) sd_bus_emit_properties_changed(
574 sd_bus_message_get_bus(m
),
575 "/org/freedesktop/hostname1",
576 "org.freedesktop.hostname1",
577 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
578 prop
== PROP_DEPLOYMENT
? "Deployment" :
579 prop
== PROP_LOCATION
? "Location" :
580 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
582 return sd_bus_reply_method_return(m
, NULL
);
585 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
586 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
589 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
590 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
593 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
594 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
597 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
598 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
601 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
602 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
605 static const sd_bus_vtable hostname_vtable
[] = {
606 SD_BUS_VTABLE_START(0),
607 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
608 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
609 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
610 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
611 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
612 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
613 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
614 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
615 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
616 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
617 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
618 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
619 SD_BUS_PROPERTY("HomeURL", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOME_URL
, SD_BUS_VTABLE_PROPERTY_CONST
),
620 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
621 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
622 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
623 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
624 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
625 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
626 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
630 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
631 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
638 r
= sd_bus_default_system(&bus
);
640 return log_error_errno(r
, "Failed to get system bus connection: %m");
642 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
644 return log_error_errno(r
, "Failed to register object: %m");
646 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.hostname1", 0, NULL
, NULL
);
648 return log_error_errno(r
, "Failed to request name: %m");
650 r
= sd_bus_attach_event(bus
, event
, 0);
652 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
654 *_bus
= TAKE_PTR(bus
);
659 int main(int argc
, char *argv
[]) {
660 Context context
= {};
661 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
662 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
665 log_set_target(LOG_TARGET_AUTO
);
666 log_parse_environment();
673 log_error("This program takes no arguments.");
678 r
= sd_event_default(&event
);
680 log_error_errno(r
, "Failed to allocate event loop: %m");
684 sd_event_set_watchdog(event
, true);
686 r
= connect_bus(&context
, event
, &bus
);
690 r
= context_read_data(&context
);
692 log_error_errno(r
, "Failed to read hostname and machine information: %m");
696 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
698 log_error_errno(r
, "Failed to run event loop: %m");
703 context_free(&context
);
705 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;