1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include <sys/utsname.h>
8 #include "alloc-util.h"
9 #include "bus-common-errors.h"
12 #include "env-file-label.h"
15 #include "fileio-label.h"
17 #include "hostname-util.h"
18 #include "id128-util.h"
19 #include "main-func.h"
20 #include "missing_capability.h"
21 #include "nscd-flush.h"
23 #include "parse-util.h"
24 #include "path-util.h"
25 #include "selinux-util.h"
26 #include "signal-util.h"
28 #include "user-util.h"
32 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
51 typedef struct Context
{
52 char *data
[_PROP_MAX
];
53 Hashmap
*polkit_registry
;
58 static void context_reset(Context
*c
) {
63 for (p
= 0; p
< _PROP_MAX
; p
++)
64 c
->data
[p
] = mfree(c
->data
[p
]);
67 static void context_clear(Context
*c
) {
71 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
74 static int context_read_data(Context
*c
) {
82 assert_se(uname(&u
) >= 0);
83 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
84 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
85 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
86 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
87 !c
->data
[PROP_KERNEL_VERSION
])
90 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
91 if (!c
->data
[PROP_HOSTNAME
])
94 r
= read_etc_hostname(NULL
, &c
->data
[PROP_STATIC_HOSTNAME
]);
95 if (r
< 0 && r
!= -ENOENT
)
98 r
= parse_env_file(NULL
, "/etc/machine-info",
99 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
100 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
101 "CHASSIS", &c
->data
[PROP_CHASSIS
],
102 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
103 "LOCATION", &c
->data
[PROP_LOCATION
]);
104 if (r
< 0 && r
!= -ENOENT
)
107 r
= parse_os_release(NULL
,
108 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
109 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
110 "HOME_URL", &c
->data
[PROP_HOME_URL
],
112 if (r
< 0 && r
!= -ENOENT
)
115 r
= id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID
, &c
->uuid
);
117 log_full_errno(r
== -ENOENT
? LOG_DEBUG
: LOG_WARNING
, r
,
118 "Failed to read product UUID, ignoring: %m");
119 else if (sd_id128_is_null(c
->uuid
) || sd_id128_is_allf(c
->uuid
))
120 log_debug("DMI product UUID " SD_ID128_FORMAT_STR
" is all 0x00 or all 0xFF, ignoring.", SD_ID128_FORMAT_VAL(c
->uuid
));
127 static bool valid_chassis(const char *chassis
) {
130 return nulstr_contains(
144 static bool valid_deployment(const char *deployment
) {
147 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
150 static const char* fallback_chassis(void) {
155 v
= detect_virtualization();
156 if (VIRTUALIZATION_IS_VM(v
))
158 if (VIRTUALIZATION_IS_CONTAINER(v
))
161 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
165 r
= safe_atou(type
, &t
);
170 /* We only list the really obvious cases here. The DMI data is unreliable enough, so let's not do any
171 additional guesswork on top of that.
173 See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here:
175 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
180 case 0x3: /* Desktop */
181 case 0x4: /* Low Profile Desktop */
182 case 0x6: /* Mini Tower */
183 case 0x7: /* Tower */
186 case 0x8: /* Portable */
187 case 0x9: /* Laptop */
188 case 0xA: /* Notebook */
189 case 0xE: /* Sub Notebook */
192 case 0xB: /* Hand Held */
195 case 0x11: /* Main Server Chassis */
196 case 0x1C: /* Blade */
197 case 0x1D: /* Blade Enclosure */
200 case 0x1E: /* Tablet */
203 case 0x1F: /* Convertible */
204 case 0x20: /* Detachable */
205 return "convertible";
209 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
213 r
= safe_atou(type
, &t
);
218 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
220 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
222 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
227 case 1: /* Desktop */
228 case 3: /* Workstation */
229 case 6: /* Appliance PC */
235 case 4: /* Enterprise Server */
236 case 5: /* SOHO Server */
237 case 7: /* Performance Server */
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");
262 static bool hostname_is_useful(const char *hn
) {
263 return !isempty(hn
) && !is_localhost(hn
);
266 static int context_update_kernel_hostname(Context
*c
) {
267 const char *static_hn
;
272 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
274 /* /etc/hostname with something other than "localhost"
275 * has the highest preference ... */
276 if (hostname_is_useful(static_hn
))
279 /* ... the transient host name, (ie: DHCP) comes next ... */
280 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
281 hn
= c
->data
[PROP_HOSTNAME
];
283 /* ... fallback to static "localhost.*" ignored above ... */
284 else if (!isempty(static_hn
))
287 /* ... and the ultimate fallback */
289 hn
= FALLBACK_HOSTNAME
;
291 if (sethostname_idempotent(hn
) < 0)
294 (void) nscd_flush_cache(STRV_MAKE("hosts"));
299 static int context_write_data_static_hostname(Context
*c
) {
303 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
305 if (unlink("/etc/hostname") < 0)
306 return errno
== ENOENT
? 0 : -errno
;
310 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
313 static int context_write_data_machine_info(Context
*c
) {
315 static const char * const name
[_PROP_MAX
] = {
316 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
317 [PROP_ICON_NAME
] = "ICON_NAME",
318 [PROP_CHASSIS
] = "CHASSIS",
319 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
320 [PROP_LOCATION
] = "LOCATION",
323 _cleanup_strv_free_
char **l
= NULL
;
328 r
= load_env_file(NULL
, "/etc/machine-info", &l
);
329 if (r
< 0 && r
!= -ENOENT
)
332 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
333 _cleanup_free_
char *t
= NULL
;
338 if (isempty(c
->data
[p
])) {
339 strv_env_unset(l
, name
[p
]);
343 t
= strjoin(name
[p
], "=", c
->data
[p
]);
347 u
= strv_env_set(l
, t
);
351 strv_free_and_replace(l
, u
);
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
;
417 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
422 name
= c
->data
[PROP_STATIC_HOSTNAME
];
425 name
= FALLBACK_HOSTNAME
;
427 if (!hostname_is_valid(name
, false))
428 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
430 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
431 return sd_bus_reply_method_return(m
, NULL
);
433 r
= bus_verify_polkit_async(
436 "org.freedesktop.hostname1.set-hostname",
445 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
447 r
= free_and_strdup(&c
->data
[PROP_HOSTNAME
], name
);
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: %m");
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
);
477 name
= empty_to_null(name
);
479 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
480 return sd_bus_reply_method_return(m
, NULL
);
482 if (!isempty(name
) && !hostname_is_valid(name
, false))
483 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
485 r
= bus_verify_polkit_async(
488 "org.freedesktop.hostname1.set-static-hostname",
497 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
499 r
= free_and_strdup(&c
->data
[PROP_STATIC_HOSTNAME
], name
);
503 r
= context_update_kernel_hostname(c
);
505 log_error_errno(r
, "Failed to set host name: %m");
506 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
509 r
= context_write_data_static_hostname(c
);
511 log_error_errno(r
, "Failed to write static host name: %m");
512 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %m");
515 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
517 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
519 return sd_bus_reply_method_return(m
, NULL
);
522 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
530 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
534 name
= empty_to_null(name
);
536 if (streq_ptr(name
, c
->data
[prop
]))
537 return sd_bus_reply_method_return(m
, NULL
);
539 if (!isempty(name
)) {
540 /* The icon name might ultimately be used as file
541 * name, so better be safe than sorry */
543 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
544 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
545 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
546 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
547 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
548 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
549 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
550 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
551 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
552 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
555 /* Since the pretty hostname should always be changed at the
556 * same time as the static one, use the same policy action for
559 r
= bus_verify_polkit_async(
562 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
571 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
573 r
= free_and_strdup(&c
->data
[prop
], name
);
577 r
= context_write_data_machine_info(c
);
579 log_error_errno(r
, "Failed to write machine info: %m");
580 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %m");
583 log_info("Changed %s to '%s'",
584 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
585 prop
== PROP_DEPLOYMENT
? "deployment" :
586 prop
== PROP_LOCATION
? "location" :
587 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
589 (void) sd_bus_emit_properties_changed(
590 sd_bus_message_get_bus(m
),
591 "/org/freedesktop/hostname1",
592 "org.freedesktop.hostname1",
593 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
594 prop
== PROP_DEPLOYMENT
? "Deployment" :
595 prop
== PROP_LOCATION
? "Location" :
596 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
598 return sd_bus_reply_method_return(m
, NULL
);
601 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
602 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
605 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
606 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
609 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
610 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
613 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
614 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
617 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
618 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
621 static int method_get_product_uuid(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
622 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
623 Context
*c
= userdata
;
630 return sd_bus_error_set(error
, BUS_ERROR_NO_PRODUCT_UUID
, "Failed to read product UUID from /sys/class/dmi/id/product_uuid");
632 r
= sd_bus_message_read(m
, "b", &interactive
);
636 r
= bus_verify_polkit_async(
639 "org.freedesktop.hostname1.get-product-uuid",
648 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
650 r
= sd_bus_message_new_method_return(m
, &reply
);
654 r
= sd_bus_message_append_array(reply
, 'y', &c
->uuid
, sizeof(c
->uuid
));
658 return sd_bus_send(NULL
, reply
, NULL
);
661 static const sd_bus_vtable hostname_vtable
[] = {
662 SD_BUS_VTABLE_START(0),
663 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
664 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
665 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
666 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
667 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
668 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
669 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
670 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
671 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
672 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
673 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
674 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
675 SD_BUS_PROPERTY("HomeURL", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOME_URL
, SD_BUS_VTABLE_PROPERTY_CONST
),
676 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
677 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
678 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
679 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
680 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
681 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
682 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
683 SD_BUS_METHOD("GetProductUUID", "b", "ay", method_get_product_uuid
, SD_BUS_VTABLE_UNPRIVILEGED
),
687 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
688 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
695 r
= sd_bus_default_system(&bus
);
697 return log_error_errno(r
, "Failed to get system bus connection: %m");
699 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
701 return log_error_errno(r
, "Failed to register object: %m");
703 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.hostname1", 0, NULL
, NULL
);
705 return log_error_errno(r
, "Failed to request name: %m");
707 r
= sd_bus_attach_event(bus
, event
, 0);
709 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
711 *_bus
= TAKE_PTR(bus
);
716 static int run(int argc
, char *argv
[]) {
717 _cleanup_(context_clear
) Context context
= {};
718 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
719 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
728 log_error("This program takes no arguments.");
732 assert_se(sigprocmask_many(SIG_BLOCK
, NULL
, SIGTERM
, SIGINT
, -1) >= 0);
734 r
= sd_event_default(&event
);
736 return log_error_errno(r
, "Failed to allocate event loop: %m");
738 (void) sd_event_set_watchdog(event
, true);
740 r
= sd_event_add_signal(event
, NULL
, SIGINT
, NULL
, NULL
);
742 return log_error_errno(r
, "Failed to install SIGINT handler: %m");
744 r
= sd_event_add_signal(event
, NULL
, SIGTERM
, NULL
, NULL
);
746 return log_error_errno(r
, "Failed to install SIGTERM handler: %m");
748 r
= connect_bus(&context
, event
, &bus
);
752 r
= context_read_data(&context
);
754 return log_error_errno(r
, "Failed to read hostname and machine information: %m");
756 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
758 return log_error_errno(r
, "Failed to run event loop: %m");
763 DEFINE_MAIN_FUNCTION(run
);