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"
15 #include "hostname-util.h"
16 #include "id128-util.h"
17 #include "main-func.h"
19 #include "parse-util.h"
20 #include "path-util.h"
21 #include "selinux-util.h"
22 #include "signal-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
;
54 static void context_reset(Context
*c
) {
59 for (p
= 0; p
< _PROP_MAX
; p
++)
60 c
->data
[p
] = mfree(c
->data
[p
]);
63 static void context_clear(Context
*c
) {
67 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
70 static int context_read_data(Context
*c
) {
78 assert_se(uname(&u
) >= 0);
79 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
80 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
81 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
82 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
83 !c
->data
[PROP_KERNEL_VERSION
])
86 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
87 if (!c
->data
[PROP_HOSTNAME
])
90 r
= read_etc_hostname(NULL
, &c
->data
[PROP_STATIC_HOSTNAME
]);
91 if (r
< 0 && r
!= -ENOENT
)
94 r
= parse_env_file(NULL
, "/etc/machine-info",
95 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
96 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
97 "CHASSIS", &c
->data
[PROP_CHASSIS
],
98 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
99 "LOCATION", &c
->data
[PROP_LOCATION
]);
100 if (r
< 0 && r
!= -ENOENT
)
103 r
= parse_os_release(NULL
,
104 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
105 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
106 "HOME_URL", &c
->data
[PROP_HOME_URL
],
108 if (r
< 0 && r
!= -ENOENT
)
111 r
= id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID
, &c
->uuid
);
113 log_info_errno(r
, "Failed to read product UUID, ignoring: %m");
114 c
->has_uuid
= (r
>= 0);
119 static bool valid_chassis(const char *chassis
) {
122 return nulstr_contains(
136 static bool valid_deployment(const char *deployment
) {
139 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
142 static const char* fallback_chassis(void) {
147 v
= detect_virtualization();
148 if (VIRTUALIZATION_IS_VM(v
))
150 if (VIRTUALIZATION_IS_CONTAINER(v
))
153 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
157 r
= safe_atou(type
, &t
);
162 /* We only list the really obvious cases here. The DMI data is unreliable enough, so let's not do any
163 additional guesswork on top of that.
165 See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here:
167 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
172 case 0x3: /* Desktop */
173 case 0x4: /* Low Profile Desktop */
174 case 0x6: /* Mini Tower */
175 case 0x7: /* Tower */
178 case 0x8: /* Portable */
179 case 0x9: /* Laptop */
180 case 0xA: /* Notebook */
181 case 0xE: /* Sub Notebook */
184 case 0xB: /* Hand Held */
187 case 0x11: /* Main Server Chassis */
188 case 0x1C: /* Blade */
189 case 0x1D: /* Blade Enclosure */
192 case 0x1E: /* Tablet */
195 case 0x1F: /* Convertible */
196 case 0x20: /* Detachable */
197 return "convertible";
201 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
205 r
= safe_atou(type
, &t
);
210 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
212 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
214 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
219 case 1: /* Desktop */
220 case 3: /* Workstation */
221 case 6: /* Appliance PC */
227 case 4: /* Enterprise Server */
228 case 5: /* SOHO Server */
229 case 7: /* Performance Server */
239 static char* context_fallback_icon_name(Context
*c
) {
244 if (!isempty(c
->data
[PROP_CHASSIS
]))
245 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
247 chassis
= fallback_chassis();
249 return strappend("computer-", chassis
);
251 return strdup("computer");
254 static bool hostname_is_useful(const char *hn
) {
255 return !isempty(hn
) && !is_localhost(hn
);
258 static int context_update_kernel_hostname(Context
*c
) {
259 const char *static_hn
;
264 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
266 /* /etc/hostname with something other than "localhost"
267 * has the highest preference ... */
268 if (hostname_is_useful(static_hn
))
271 /* ... the transient host name, (ie: DHCP) comes next ... */
272 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
273 hn
= c
->data
[PROP_HOSTNAME
];
275 /* ... fallback to static "localhost.*" ignored above ... */
276 else if (!isempty(static_hn
))
279 /* ... and the ultimate fallback */
281 hn
= FALLBACK_HOSTNAME
;
283 if (sethostname_idempotent(hn
) < 0)
289 static int context_write_data_static_hostname(Context
*c
) {
293 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
295 if (unlink("/etc/hostname") < 0)
296 return errno
== ENOENT
? 0 : -errno
;
300 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
303 static int context_write_data_machine_info(Context
*c
) {
305 static const char * const name
[_PROP_MAX
] = {
306 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
307 [PROP_ICON_NAME
] = "ICON_NAME",
308 [PROP_CHASSIS
] = "CHASSIS",
309 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
310 [PROP_LOCATION
] = "LOCATION",
313 _cleanup_strv_free_
char **l
= NULL
;
318 r
= load_env_file(NULL
, "/etc/machine-info", &l
);
319 if (r
< 0 && r
!= -ENOENT
)
322 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
323 _cleanup_free_
char *t
= NULL
;
328 if (isempty(c
->data
[p
])) {
329 strv_env_unset(l
, name
[p
]);
333 t
= strjoin(name
[p
], "=", c
->data
[p
]);
337 u
= strv_env_set(l
, t
);
341 strv_free_and_replace(l
, u
);
344 if (strv_isempty(l
)) {
345 if (unlink("/etc/machine-info") < 0)
346 return errno
== ENOENT
? 0 : -errno
;
351 return write_env_file_label("/etc/machine-info", l
);
354 static int property_get_icon_name(
357 const char *interface
,
358 const char *property
,
359 sd_bus_message
*reply
,
361 sd_bus_error
*error
) {
363 _cleanup_free_
char *n
= NULL
;
364 Context
*c
= userdata
;
367 if (isempty(c
->data
[PROP_ICON_NAME
]))
368 name
= n
= context_fallback_icon_name(c
);
370 name
= c
->data
[PROP_ICON_NAME
];
375 return sd_bus_message_append(reply
, "s", name
);
378 static int property_get_chassis(
381 const char *interface
,
382 const char *property
,
383 sd_bus_message
*reply
,
385 sd_bus_error
*error
) {
387 Context
*c
= userdata
;
390 if (isempty(c
->data
[PROP_CHASSIS
]))
391 name
= fallback_chassis();
393 name
= c
->data
[PROP_CHASSIS
];
395 return sd_bus_message_append(reply
, "s", name
);
398 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
399 Context
*c
= userdata
;
407 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
412 name
= c
->data
[PROP_STATIC_HOSTNAME
];
415 name
= FALLBACK_HOSTNAME
;
417 if (!hostname_is_valid(name
, false))
418 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
420 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
421 return sd_bus_reply_method_return(m
, NULL
);
423 r
= bus_verify_polkit_async(
426 "org.freedesktop.hostname1.set-hostname",
435 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
437 r
= free_and_strdup(&c
->data
[PROP_HOSTNAME
], name
);
441 r
= context_update_kernel_hostname(c
);
443 log_error_errno(r
, "Failed to set host name: %m");
444 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
447 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
449 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
451 return sd_bus_reply_method_return(m
, NULL
);
454 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
455 Context
*c
= userdata
;
463 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
467 name
= empty_to_null(name
);
469 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
470 return sd_bus_reply_method_return(m
, NULL
);
472 if (!isempty(name
) && !hostname_is_valid(name
, false))
473 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
475 r
= bus_verify_polkit_async(
478 "org.freedesktop.hostname1.set-static-hostname",
487 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
489 r
= free_and_strdup(&c
->data
[PROP_STATIC_HOSTNAME
], name
);
493 r
= context_update_kernel_hostname(c
);
495 log_error_errno(r
, "Failed to set host name: %m");
496 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
499 r
= context_write_data_static_hostname(c
);
501 log_error_errno(r
, "Failed to write static host name: %m");
502 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %m");
505 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
507 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
509 return sd_bus_reply_method_return(m
, NULL
);
512 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
520 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
524 name
= empty_to_null(name
);
526 if (streq_ptr(name
, c
->data
[prop
]))
527 return sd_bus_reply_method_return(m
, NULL
);
529 if (!isempty(name
)) {
530 /* The icon name might ultimately be used as file
531 * name, so better be safe than sorry */
533 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
534 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
535 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
536 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
537 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
538 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
539 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
540 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
541 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
542 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
545 /* Since the pretty hostname should always be changed at the
546 * same time as the static one, use the same policy action for
549 r
= bus_verify_polkit_async(
552 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
561 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
563 r
= free_and_strdup(&c
->data
[prop
], name
);
567 r
= context_write_data_machine_info(c
);
569 log_error_errno(r
, "Failed to write machine info: %m");
570 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %m");
573 log_info("Changed %s to '%s'",
574 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
575 prop
== PROP_DEPLOYMENT
? "deployment" :
576 prop
== PROP_LOCATION
? "location" :
577 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
579 (void) sd_bus_emit_properties_changed(
580 sd_bus_message_get_bus(m
),
581 "/org/freedesktop/hostname1",
582 "org.freedesktop.hostname1",
583 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
584 prop
== PROP_DEPLOYMENT
? "Deployment" :
585 prop
== PROP_LOCATION
? "Location" :
586 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
588 return sd_bus_reply_method_return(m
, NULL
);
591 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
592 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
595 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
596 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
599 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
600 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
603 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
604 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
607 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
608 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
611 static int method_get_product_uuid(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
612 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
613 Context
*c
= userdata
;
620 return sd_bus_error_set(error
, BUS_ERROR_NO_PRODUCT_UUID
, "Failed to read product UUID from /sys/class/dmi/id/product_uuid");
622 r
= sd_bus_message_read(m
, "b", &interactive
);
626 r
= bus_verify_polkit_async(
629 "org.freedesktop.hostname1.get-product-uuid",
638 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
640 r
= sd_bus_message_new_method_return(m
, &reply
);
644 r
= sd_bus_message_append_array(reply
, 'y', &c
->uuid
, sizeof(c
->uuid
));
648 return sd_bus_send(NULL
, reply
, NULL
);
651 static const sd_bus_vtable hostname_vtable
[] = {
652 SD_BUS_VTABLE_START(0),
653 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
654 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
655 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
656 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
657 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
658 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
659 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
660 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
661 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
662 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
663 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
664 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
665 SD_BUS_PROPERTY("HomeURL", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOME_URL
, SD_BUS_VTABLE_PROPERTY_CONST
),
666 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
667 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
668 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
669 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
670 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
671 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
672 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
673 SD_BUS_METHOD("GetProductUUID", "b", "ay", method_get_product_uuid
, SD_BUS_VTABLE_UNPRIVILEGED
),
677 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
678 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
685 r
= sd_bus_default_system(&bus
);
687 return log_error_errno(r
, "Failed to get system bus connection: %m");
689 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
691 return log_error_errno(r
, "Failed to register object: %m");
693 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.hostname1", 0, NULL
, NULL
);
695 return log_error_errno(r
, "Failed to request name: %m");
697 r
= sd_bus_attach_event(bus
, event
, 0);
699 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
701 *_bus
= TAKE_PTR(bus
);
706 static int run(int argc
, char *argv
[]) {
707 _cleanup_(context_clear
) Context context
= {};
708 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
709 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
718 log_error("This program takes no arguments.");
722 assert_se(sigprocmask_many(SIG_BLOCK
, NULL
, SIGTERM
, SIGINT
, -1) >= 0);
724 r
= sd_event_default(&event
);
726 return log_error_errno(r
, "Failed to allocate event loop: %m");
728 (void) sd_event_set_watchdog(event
, true);
730 r
= sd_event_add_signal(event
, NULL
, SIGINT
, NULL
, NULL
);
732 return log_error_errno(r
, "Failed to install SIGINT handler: %m");
734 r
= sd_event_add_signal(event
, NULL
, SIGTERM
, NULL
, NULL
);
736 return log_error_errno(r
, "Failed to install SIGTERM handler: %m");
738 r
= connect_bus(&context
, event
, &bus
);
742 r
= context_read_data(&context
);
744 return log_error_errno(r
, "Failed to read hostname and machine information: %m");
746 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
748 return log_error_errno(r
, "Failed to run event loop: %m");
753 DEFINE_MAIN_FUNCTION(run
);