1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include <sys/utsname.h>
10 #include "alloc-util.h"
11 #include "bus-common-errors.h"
14 #include "env-file-label.h"
17 #include "fileio-label.h"
19 #include "hostname-util.h"
20 #include "id128-util.h"
21 #include "main-func.h"
22 #include "missing_capability.h"
23 #include "nscd-flush.h"
24 #include "nulstr-util.h"
26 #include "parse-util.h"
27 #include "path-util.h"
28 #include "selinux-util.h"
29 #include "signal-util.h"
31 #include "user-util.h"
35 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
54 typedef struct Context
{
55 char *data
[_PROP_MAX
];
56 Hashmap
*polkit_registry
;
61 static void context_reset(Context
*c
) {
66 for (p
= 0; p
< _PROP_MAX
; p
++)
67 c
->data
[p
] = mfree(c
->data
[p
]);
70 static void context_clear(Context
*c
) {
74 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
77 static int context_read_data(Context
*c
) {
85 assert_se(uname(&u
) >= 0);
86 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
87 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
88 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
89 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
90 !c
->data
[PROP_KERNEL_VERSION
])
93 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
94 if (!c
->data
[PROP_HOSTNAME
])
97 r
= read_etc_hostname(NULL
, &c
->data
[PROP_STATIC_HOSTNAME
]);
98 if (r
< 0 && r
!= -ENOENT
)
101 r
= parse_env_file(NULL
, "/etc/machine-info",
102 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
103 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
104 "CHASSIS", &c
->data
[PROP_CHASSIS
],
105 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
106 "LOCATION", &c
->data
[PROP_LOCATION
]);
107 if (r
< 0 && r
!= -ENOENT
)
110 r
= parse_os_release(NULL
,
111 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
112 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
113 "HOME_URL", &c
->data
[PROP_HOME_URL
],
115 if (r
< 0 && r
!= -ENOENT
)
118 r
= id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID
, &c
->uuid
);
120 r
= id128_read("/sys/firmware/devicetree/base/vm,uuid", ID128_UUID
, &c
->uuid
);
122 log_full_errno(r
== -ENOENT
? LOG_DEBUG
: LOG_WARNING
, r
,
123 "Failed to read product UUID, ignoring: %m");
124 else if (sd_id128_is_null(c
->uuid
) || sd_id128_is_allf(c
->uuid
))
125 log_debug("DMI product UUID " SD_ID128_FORMAT_STR
" is all 0x00 or all 0xFF, ignoring.", SD_ID128_FORMAT_VAL(c
->uuid
));
132 static bool valid_chassis(const char *chassis
) {
135 return nulstr_contains(
149 static bool valid_deployment(const char *deployment
) {
152 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
155 static const char* fallback_chassis(void) {
160 v
= detect_virtualization();
161 if (VIRTUALIZATION_IS_VM(v
))
163 if (VIRTUALIZATION_IS_CONTAINER(v
))
166 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
170 r
= safe_atou(type
, &t
);
175 /* We only list the really obvious cases here. The DMI data is unreliable enough, so let's not do any
176 additional guesswork on top of that.
178 See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here:
180 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
185 case 0x3: /* Desktop */
186 case 0x4: /* Low Profile Desktop */
187 case 0x6: /* Mini Tower */
188 case 0x7: /* Tower */
191 case 0x8: /* Portable */
192 case 0x9: /* Laptop */
193 case 0xA: /* Notebook */
194 case 0xE: /* Sub Notebook */
197 case 0xB: /* Hand Held */
200 case 0x11: /* Main Server Chassis */
201 case 0x1C: /* Blade */
202 case 0x1D: /* Blade Enclosure */
205 case 0x1E: /* Tablet */
208 case 0x1F: /* Convertible */
209 case 0x20: /* Detachable */
210 return "convertible";
214 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
218 r
= safe_atou(type
, &t
);
223 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
225 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
227 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
232 case 1: /* Desktop */
233 case 3: /* Workstation */
234 case 6: /* Appliance PC */
240 case 4: /* Enterprise Server */
241 case 5: /* SOHO Server */
242 case 7: /* Performance Server */
252 static char* context_fallback_icon_name(Context
*c
) {
257 if (!isempty(c
->data
[PROP_CHASSIS
]))
258 return strjoin("computer-", c
->data
[PROP_CHASSIS
]);
260 chassis
= fallback_chassis();
262 return strjoin("computer-", chassis
);
264 return strdup("computer");
267 static bool hostname_is_useful(const char *hn
) {
268 return !isempty(hn
) && !is_localhost(hn
);
271 static int context_update_kernel_hostname(Context
*c
) {
272 const char *static_hn
;
277 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
279 /* /etc/hostname with something other than "localhost"
280 * has the highest preference ... */
281 if (hostname_is_useful(static_hn
))
284 /* ... the transient host name, (ie: DHCP) comes next ... */
285 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
286 hn
= c
->data
[PROP_HOSTNAME
];
288 /* ... fallback to static "localhost.*" ignored above ... */
289 else if (!isempty(static_hn
))
292 /* ... and the ultimate fallback */
294 hn
= FALLBACK_HOSTNAME
;
296 if (sethostname_idempotent(hn
) < 0)
299 (void) nscd_flush_cache(STRV_MAKE("hosts"));
304 static int context_write_data_static_hostname(Context
*c
) {
308 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
310 if (unlink("/etc/hostname") < 0)
311 return errno
== ENOENT
? 0 : -errno
;
315 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
318 static int context_write_data_machine_info(Context
*c
) {
320 static const char * const name
[_PROP_MAX
] = {
321 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
322 [PROP_ICON_NAME
] = "ICON_NAME",
323 [PROP_CHASSIS
] = "CHASSIS",
324 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
325 [PROP_LOCATION
] = "LOCATION",
328 _cleanup_strv_free_
char **l
= NULL
;
333 r
= load_env_file(NULL
, "/etc/machine-info", &l
);
334 if (r
< 0 && r
!= -ENOENT
)
337 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
338 _cleanup_free_
char *t
= NULL
;
343 if (isempty(c
->data
[p
])) {
344 strv_env_unset(l
, name
[p
]);
348 t
= strjoin(name
[p
], "=", c
->data
[p
]);
352 u
= strv_env_set(l
, t
);
356 strv_free_and_replace(l
, u
);
359 if (strv_isempty(l
)) {
360 if (unlink("/etc/machine-info") < 0)
361 return errno
== ENOENT
? 0 : -errno
;
366 return write_env_file_label("/etc/machine-info", l
);
369 static int property_get_icon_name(
372 const char *interface
,
373 const char *property
,
374 sd_bus_message
*reply
,
376 sd_bus_error
*error
) {
378 _cleanup_free_
char *n
= NULL
;
379 Context
*c
= userdata
;
382 if (isempty(c
->data
[PROP_ICON_NAME
]))
383 name
= n
= context_fallback_icon_name(c
);
385 name
= c
->data
[PROP_ICON_NAME
];
390 return sd_bus_message_append(reply
, "s", name
);
393 static int property_get_chassis(
396 const char *interface
,
397 const char *property
,
398 sd_bus_message
*reply
,
400 sd_bus_error
*error
) {
402 Context
*c
= userdata
;
405 if (isempty(c
->data
[PROP_CHASSIS
]))
406 name
= fallback_chassis();
408 name
= c
->data
[PROP_CHASSIS
];
410 return sd_bus_message_append(reply
, "s", name
);
413 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
414 Context
*c
= userdata
;
422 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
427 name
= c
->data
[PROP_STATIC_HOSTNAME
];
430 name
= FALLBACK_HOSTNAME
;
432 if (!hostname_is_valid(name
, false))
433 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
435 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
436 return sd_bus_reply_method_return(m
, NULL
);
438 r
= bus_verify_polkit_async(
441 "org.freedesktop.hostname1.set-hostname",
450 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
452 r
= free_and_strdup(&c
->data
[PROP_HOSTNAME
], name
);
456 r
= context_update_kernel_hostname(c
);
458 log_error_errno(r
, "Failed to set host name: %m");
459 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
462 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
464 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
466 return sd_bus_reply_method_return(m
, NULL
);
469 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
470 Context
*c
= userdata
;
478 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
482 name
= empty_to_null(name
);
484 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
485 return sd_bus_reply_method_return(m
, NULL
);
487 if (!isempty(name
) && !hostname_is_valid(name
, false))
488 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
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 */
504 r
= free_and_strdup(&c
->data
[PROP_STATIC_HOSTNAME
], name
);
508 r
= context_update_kernel_hostname(c
);
510 log_error_errno(r
, "Failed to set host name: %m");
511 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
514 r
= context_write_data_static_hostname(c
);
516 log_error_errno(r
, "Failed to write static host name: %m");
517 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %m");
520 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
522 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
524 return sd_bus_reply_method_return(m
, NULL
);
527 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
535 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
539 name
= empty_to_null(name
);
541 if (streq_ptr(name
, c
->data
[prop
]))
542 return sd_bus_reply_method_return(m
, NULL
);
544 if (!isempty(name
)) {
545 /* The icon name might ultimately be used as file
546 * name, so better be safe than sorry */
548 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
549 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
550 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
551 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
552 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
553 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
554 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
555 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
556 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
557 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
560 /* Since the pretty hostname should always be changed at the
561 * same time as the static one, use the same policy action for
564 r
= bus_verify_polkit_async(
567 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
576 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
578 r
= free_and_strdup(&c
->data
[prop
], name
);
582 r
= context_write_data_machine_info(c
);
584 log_error_errno(r
, "Failed to write machine info: %m");
585 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %m");
588 log_info("Changed %s to '%s'",
589 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
590 prop
== PROP_DEPLOYMENT
? "deployment" :
591 prop
== PROP_LOCATION
? "location" :
592 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
594 (void) sd_bus_emit_properties_changed(
595 sd_bus_message_get_bus(m
),
596 "/org/freedesktop/hostname1",
597 "org.freedesktop.hostname1",
598 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
599 prop
== PROP_DEPLOYMENT
? "Deployment" :
600 prop
== PROP_LOCATION
? "Location" :
601 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
603 return sd_bus_reply_method_return(m
, NULL
);
606 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
607 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
610 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
611 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
614 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
615 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
618 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
619 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
622 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
623 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
626 static int method_get_product_uuid(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
627 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
628 Context
*c
= userdata
;
635 return sd_bus_error_set(error
, BUS_ERROR_NO_PRODUCT_UUID
, "Failed to read product UUID from /sys/class/dmi/id/product_uuid");
637 r
= sd_bus_message_read(m
, "b", &interactive
);
641 r
= bus_verify_polkit_async(
644 "org.freedesktop.hostname1.get-product-uuid",
653 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
655 r
= sd_bus_message_new_method_return(m
, &reply
);
659 r
= sd_bus_message_append_array(reply
, 'y', &c
->uuid
, sizeof(c
->uuid
));
663 return sd_bus_send(NULL
, reply
, NULL
);
666 static const sd_bus_vtable hostname_vtable
[] = {
667 SD_BUS_VTABLE_START(0),
668 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
669 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
670 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
671 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
672 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
673 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
674 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
675 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
676 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
677 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
678 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
679 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
680 SD_BUS_PROPERTY("HomeURL", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOME_URL
, SD_BUS_VTABLE_PROPERTY_CONST
),
681 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
682 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
683 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
684 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
685 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
686 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
687 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
688 SD_BUS_METHOD("GetProductUUID", "b", "ay", method_get_product_uuid
, SD_BUS_VTABLE_UNPRIVILEGED
),
692 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
693 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
700 r
= sd_bus_default_system(&bus
);
702 return log_error_errno(r
, "Failed to get system bus connection: %m");
704 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
706 return log_error_errno(r
, "Failed to register object: %m");
708 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.hostname1", 0, NULL
, NULL
);
710 return log_error_errno(r
, "Failed to request name: %m");
712 r
= sd_bus_attach_event(bus
, event
, 0);
714 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
716 *_bus
= TAKE_PTR(bus
);
721 static int run(int argc
, char *argv
[]) {
722 _cleanup_(context_clear
) Context context
= {};
723 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
724 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
733 log_error("This program takes no arguments.");
737 assert_se(sigprocmask_many(SIG_BLOCK
, NULL
, SIGTERM
, SIGINT
, -1) >= 0);
739 r
= sd_event_default(&event
);
741 return log_error_errno(r
, "Failed to allocate event loop: %m");
743 (void) sd_event_set_watchdog(event
, true);
745 r
= sd_event_add_signal(event
, NULL
, SIGINT
, NULL
, NULL
);
747 return log_error_errno(r
, "Failed to install SIGINT handler: %m");
749 r
= sd_event_add_signal(event
, NULL
, SIGTERM
, NULL
, NULL
);
751 return log_error_errno(r
, "Failed to install SIGTERM handler: %m");
753 r
= connect_bus(&context
, event
, &bus
);
757 r
= context_read_data(&context
);
759 return log_error_errno(r
, "Failed to read hostname and machine information: %m");
761 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
763 return log_error_errno(r
, "Failed to run event loop: %m");
768 DEFINE_MAIN_FUNCTION(run
);