1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include <sys/utsname.h>
8 #include "alloc-util.h"
9 #include "bus-common-errors.h"
13 #include "fileio-label.h"
14 #include "hostname-util.h"
15 #include "id128-util.h"
17 #include "parse-util.h"
18 #include "path-util.h"
19 #include "selinux-util.h"
20 #include "signal-util.h"
22 #include "user-util.h"
26 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
45 typedef struct Context
{
46 char *data
[_PROP_MAX
];
47 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
)
110 r
= id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID
, &c
->uuid
);
112 log_info_errno(r
, "Failed to read product UUID, ignoring: %m");
113 c
->has_uuid
= (r
>= 0);
118 static bool valid_chassis(const char *chassis
) {
121 return nulstr_contains(
135 static bool valid_deployment(const char *deployment
) {
138 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
141 static const char* fallback_chassis(void) {
146 v
= detect_virtualization();
147 if (VIRTUALIZATION_IS_VM(v
))
149 if (VIRTUALIZATION_IS_CONTAINER(v
))
152 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
156 r
= safe_atou(type
, &t
);
161 /* We only list the really obvious cases here. The DMI data is unreliable enough, so let's not do any
162 additional guesswork on top of that.
164 See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here:
166 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
171 case 0x3: /* Desktop */
172 case 0x4: /* Low Profile Desktop */
173 case 0x6: /* Mini Tower */
174 case 0x7: /* Tower */
177 case 0x8: /* Portable */
178 case 0x9: /* Laptop */
179 case 0xA: /* Notebook */
180 case 0xE: /* Sub Notebook */
183 case 0xB: /* Hand Held */
186 case 0x11: /* Main Server Chassis */
187 case 0x1C: /* Blade */
188 case 0x1D: /* Blade Enclosure */
191 case 0x1E: /* Tablet */
194 case 0x1F: /* Convertible */
195 case 0x20: /* Detachable */
196 return "convertible";
200 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
204 r
= safe_atou(type
, &t
);
209 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
211 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
213 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
218 case 1: /* Desktop */
219 case 3: /* Workstation */
220 case 6: /* Appliance PC */
226 case 4: /* Enterprise Server */
227 case 5: /* SOHO Server */
228 case 7: /* Performance Server */
238 static char* context_fallback_icon_name(Context
*c
) {
243 if (!isempty(c
->data
[PROP_CHASSIS
]))
244 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
246 chassis
= fallback_chassis();
248 return strappend("computer-", chassis
);
250 return strdup("computer");
253 static bool hostname_is_useful(const char *hn
) {
254 return !isempty(hn
) && !is_localhost(hn
);
257 static int context_update_kernel_hostname(Context
*c
) {
258 const char *static_hn
;
263 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
265 /* /etc/hostname with something other than "localhost"
266 * has the highest preference ... */
267 if (hostname_is_useful(static_hn
))
270 /* ... the transient host name, (ie: DHCP) comes next ... */
271 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
272 hn
= c
->data
[PROP_HOSTNAME
];
274 /* ... fallback to static "localhost.*" ignored above ... */
275 else if (!isempty(static_hn
))
278 /* ... and the ultimate fallback */
280 hn
= FALLBACK_HOSTNAME
;
282 if (sethostname_idempotent(hn
) < 0)
288 static int context_write_data_static_hostname(Context
*c
) {
292 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
294 if (unlink("/etc/hostname") < 0)
295 return errno
== ENOENT
? 0 : -errno
;
299 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
302 static int context_write_data_machine_info(Context
*c
) {
304 static const char * const name
[_PROP_MAX
] = {
305 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
306 [PROP_ICON_NAME
] = "ICON_NAME",
307 [PROP_CHASSIS
] = "CHASSIS",
308 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
309 [PROP_LOCATION
] = "LOCATION",
312 _cleanup_strv_free_
char **l
= NULL
;
317 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
318 if (r
< 0 && r
!= -ENOENT
)
321 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
322 _cleanup_free_
char *t
= NULL
;
327 if (isempty(c
->data
[p
])) {
328 strv_env_unset(l
, name
[p
]);
332 t
= strjoin(name
[p
], "=", c
->data
[p
]);
336 u
= strv_env_set(l
, t
);
340 strv_free_and_replace(l
, u
);
343 if (strv_isempty(l
)) {
344 if (unlink("/etc/machine-info") < 0)
345 return errno
== ENOENT
? 0 : -errno
;
350 return write_env_file_label("/etc/machine-info", l
);
353 static int property_get_icon_name(
356 const char *interface
,
357 const char *property
,
358 sd_bus_message
*reply
,
360 sd_bus_error
*error
) {
362 _cleanup_free_
char *n
= NULL
;
363 Context
*c
= userdata
;
366 if (isempty(c
->data
[PROP_ICON_NAME
]))
367 name
= n
= context_fallback_icon_name(c
);
369 name
= c
->data
[PROP_ICON_NAME
];
374 return sd_bus_message_append(reply
, "s", name
);
377 static int property_get_chassis(
380 const char *interface
,
381 const char *property
,
382 sd_bus_message
*reply
,
384 sd_bus_error
*error
) {
386 Context
*c
= userdata
;
389 if (isempty(c
->data
[PROP_CHASSIS
]))
390 name
= fallback_chassis();
392 name
= c
->data
[PROP_CHASSIS
];
394 return sd_bus_message_append(reply
, "s", name
);
397 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
398 Context
*c
= userdata
;
406 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
411 name
= c
->data
[PROP_STATIC_HOSTNAME
];
414 name
= FALLBACK_HOSTNAME
;
416 if (!hostname_is_valid(name
, false))
417 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
419 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
420 return sd_bus_reply_method_return(m
, NULL
);
422 r
= bus_verify_polkit_async(
425 "org.freedesktop.hostname1.set-hostname",
434 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
436 r
= free_and_strdup(&c
->data
[PROP_HOSTNAME
], name
);
440 r
= context_update_kernel_hostname(c
);
442 log_error_errno(r
, "Failed to set host name: %m");
443 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
446 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
448 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
450 return sd_bus_reply_method_return(m
, NULL
);
453 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
454 Context
*c
= userdata
;
462 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
466 name
= empty_to_null(name
);
468 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
469 return sd_bus_reply_method_return(m
, NULL
);
471 if (!isempty(name
) && !hostname_is_valid(name
, false))
472 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
474 r
= bus_verify_polkit_async(
477 "org.freedesktop.hostname1.set-static-hostname",
486 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
488 r
= free_and_strdup(&c
->data
[PROP_STATIC_HOSTNAME
], name
);
492 r
= context_update_kernel_hostname(c
);
494 log_error_errno(r
, "Failed to set host name: %m");
495 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
498 r
= context_write_data_static_hostname(c
);
500 log_error_errno(r
, "Failed to write static host name: %m");
501 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %m");
504 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
506 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
508 return sd_bus_reply_method_return(m
, NULL
);
511 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
519 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
523 name
= empty_to_null(name
);
525 if (streq_ptr(name
, c
->data
[prop
]))
526 return sd_bus_reply_method_return(m
, NULL
);
528 if (!isempty(name
)) {
529 /* The icon name might ultimately be used as file
530 * name, so better be safe than sorry */
532 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
533 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
534 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
535 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
536 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
537 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
538 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
539 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
540 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
541 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
544 /* Since the pretty hostname should always be changed at the
545 * same time as the static one, use the same policy action for
548 r
= bus_verify_polkit_async(
551 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
560 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
562 r
= free_and_strdup(&c
->data
[prop
], name
);
566 r
= context_write_data_machine_info(c
);
568 log_error_errno(r
, "Failed to write machine info: %m");
569 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %m");
572 log_info("Changed %s to '%s'",
573 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
574 prop
== PROP_DEPLOYMENT
? "deployment" :
575 prop
== PROP_LOCATION
? "location" :
576 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
578 (void) sd_bus_emit_properties_changed(
579 sd_bus_message_get_bus(m
),
580 "/org/freedesktop/hostname1",
581 "org.freedesktop.hostname1",
582 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
583 prop
== PROP_DEPLOYMENT
? "Deployment" :
584 prop
== PROP_LOCATION
? "Location" :
585 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
587 return sd_bus_reply_method_return(m
, NULL
);
590 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
591 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
594 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
595 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
598 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
599 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
602 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
603 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
606 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
607 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
610 static int method_get_product_uuid(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
611 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
612 Context
*c
= userdata
;
619 return sd_bus_error_set(error
, BUS_ERROR_NO_PRODUCT_UUID
, "Failed to read product UUID from /sys/class/dmi/id/product_uuid");
621 r
= sd_bus_message_read(m
, "b", &interactive
);
625 r
= bus_verify_polkit_async(
628 "org.freedesktop.hostname1.get-product-uuid",
637 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
639 r
= sd_bus_message_new_method_return(m
, &reply
);
643 r
= sd_bus_message_append_array(reply
, 'y', &c
->uuid
, sizeof(c
->uuid
));
647 return sd_bus_send(NULL
, reply
, NULL
);
650 static const sd_bus_vtable hostname_vtable
[] = {
651 SD_BUS_VTABLE_START(0),
652 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
653 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
654 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
655 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
656 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
657 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
658 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
659 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
660 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
661 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
662 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
663 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
664 SD_BUS_PROPERTY("HomeURL", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOME_URL
, SD_BUS_VTABLE_PROPERTY_CONST
),
665 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
666 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
667 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
668 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
669 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
670 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
671 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
672 SD_BUS_METHOD("GetProductUUID", "b", "ay", method_get_product_uuid
, SD_BUS_VTABLE_UNPRIVILEGED
),
676 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
677 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
684 r
= sd_bus_default_system(&bus
);
686 return log_error_errno(r
, "Failed to get system bus connection: %m");
688 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
690 return log_error_errno(r
, "Failed to register object: %m");
692 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.hostname1", 0, NULL
, NULL
);
694 return log_error_errno(r
, "Failed to request name: %m");
696 r
= sd_bus_attach_event(bus
, event
, 0);
698 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
700 *_bus
= TAKE_PTR(bus
);
705 int main(int argc
, char *argv
[]) {
706 Context context
= {};
707 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
708 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
711 log_set_target(LOG_TARGET_AUTO
);
712 log_parse_environment();
719 log_error("This program takes no arguments.");
724 assert_se(sigprocmask_many(SIG_BLOCK
, NULL
, SIGTERM
, SIGINT
, -1) >= 0);
726 r
= sd_event_default(&event
);
728 log_error_errno(r
, "Failed to allocate event loop: %m");
732 (void) sd_event_set_watchdog(event
, true);
734 r
= sd_event_add_signal(event
, NULL
, SIGINT
, NULL
, NULL
);
736 log_error_errno(r
, "Failed to install SIGINT handler: %m");
740 r
= sd_event_add_signal(event
, NULL
, SIGTERM
, NULL
, NULL
);
742 log_error_errno(r
, "Failed to install SIGTERM handler: %m");
746 r
= connect_bus(&context
, event
, &bus
);
750 r
= context_read_data(&context
);
752 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 log_error_errno(r
, "Failed to run event loop: %m");
763 context_free(&context
);
765 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;