1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include <sys/utsname.h>
8 #include "alloc-util.h"
12 #include "fileio-label.h"
13 #include "hostname-util.h"
15 #include "parse-util.h"
16 #include "path-util.h"
17 #include "selinux-util.h"
19 #include "user-util.h"
23 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
42 typedef struct Context
{
43 char *data
[_PROP_MAX
];
44 Hashmap
*polkit_registry
;
47 static void context_reset(Context
*c
) {
52 for (p
= 0; p
< _PROP_MAX
; p
++)
53 c
->data
[p
] = mfree(c
->data
[p
]);
56 static void context_free(Context
*c
) {
60 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
63 static int context_read_data(Context
*c
) {
71 assert_se(uname(&u
) >= 0);
72 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
73 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
74 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
75 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
76 !c
->data
[PROP_KERNEL_VERSION
])
79 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
80 if (!c
->data
[PROP_HOSTNAME
])
83 r
= read_etc_hostname(NULL
, &c
->data
[PROP_STATIC_HOSTNAME
]);
84 if (r
< 0 && r
!= -ENOENT
)
87 r
= parse_env_file(NULL
, "/etc/machine-info", NEWLINE
,
88 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
89 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
90 "CHASSIS", &c
->data
[PROP_CHASSIS
],
91 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
92 "LOCATION", &c
->data
[PROP_LOCATION
],
94 if (r
< 0 && r
!= -ENOENT
)
97 r
= parse_os_release(NULL
,
98 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
99 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
100 "HOME_URL", &c
->data
[PROP_HOME_URL
],
102 if (r
< 0 && r
!= -ENOENT
)
108 static bool valid_chassis(const char *chassis
) {
111 return nulstr_contains(
125 static bool valid_deployment(const char *deployment
) {
128 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
131 static const char* fallback_chassis(void) {
136 v
= detect_virtualization();
137 if (VIRTUALIZATION_IS_VM(v
))
139 if (VIRTUALIZATION_IS_CONTAINER(v
))
142 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
146 r
= safe_atou(type
, &t
);
151 /* We only list the really obvious cases here. The DMI data is unreliable enough, so let's not do any
152 additional guesswork on top of that.
154 See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here:
156 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
161 case 0x3: /* Desktop */
162 case 0x4: /* Low Profile Desktop */
163 case 0x6: /* Mini Tower */
164 case 0x7: /* Tower */
167 case 0x8: /* Portable */
168 case 0x9: /* Laptop */
169 case 0xA: /* Notebook */
170 case 0xE: /* Sub Notebook */
173 case 0xB: /* Hand Held */
176 case 0x11: /* Main Server Chassis */
177 case 0x1C: /* Blade */
178 case 0x1D: /* Blade Enclosure */
181 case 0x1E: /* Tablet */
184 case 0x1F: /* Convertible */
185 case 0x20: /* Detachable */
186 return "convertible";
190 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
194 r
= safe_atou(type
, &t
);
199 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
201 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
203 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
208 case 1: /* Desktop */
209 case 3: /* Workstation */
210 case 6: /* Appliance PC */
216 case 4: /* Enterprise Server */
217 case 5: /* SOHO Server */
218 case 7: /* Performance Server */
228 static char* context_fallback_icon_name(Context
*c
) {
233 if (!isempty(c
->data
[PROP_CHASSIS
]))
234 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
236 chassis
= fallback_chassis();
238 return strappend("computer-", chassis
);
240 return strdup("computer");
243 static bool hostname_is_useful(const char *hn
) {
244 return !isempty(hn
) && !is_localhost(hn
);
247 static int context_update_kernel_hostname(Context
*c
) {
248 const char *static_hn
;
253 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
255 /* /etc/hostname with something other than "localhost"
256 * has the highest preference ... */
257 if (hostname_is_useful(static_hn
))
260 /* ... the transient host name, (ie: DHCP) comes next ... */
261 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
262 hn
= c
->data
[PROP_HOSTNAME
];
264 /* ... fallback to static "localhost.*" ignored above ... */
265 else if (!isempty(static_hn
))
268 /* ... and the ultimate fallback */
270 hn
= FALLBACK_HOSTNAME
;
272 if (sethostname_idempotent(hn
) < 0)
278 static int context_write_data_static_hostname(Context
*c
) {
282 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
284 if (unlink("/etc/hostname") < 0)
285 return errno
== ENOENT
? 0 : -errno
;
289 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
292 static int context_write_data_machine_info(Context
*c
) {
294 static const char * const name
[_PROP_MAX
] = {
295 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
296 [PROP_ICON_NAME
] = "ICON_NAME",
297 [PROP_CHASSIS
] = "CHASSIS",
298 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
299 [PROP_LOCATION
] = "LOCATION",
302 _cleanup_strv_free_
char **l
= NULL
;
307 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
308 if (r
< 0 && r
!= -ENOENT
)
311 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
312 _cleanup_free_
char *t
= NULL
;
317 if (isempty(c
->data
[p
])) {
318 strv_env_unset(l
, name
[p
]);
322 t
= strjoin(name
[p
], "=", c
->data
[p
]);
326 u
= strv_env_set(l
, t
);
330 strv_free_and_replace(l
, u
);
333 if (strv_isempty(l
)) {
334 if (unlink("/etc/machine-info") < 0)
335 return errno
== ENOENT
? 0 : -errno
;
340 return write_env_file_label("/etc/machine-info", l
);
343 static int property_get_icon_name(
346 const char *interface
,
347 const char *property
,
348 sd_bus_message
*reply
,
350 sd_bus_error
*error
) {
352 _cleanup_free_
char *n
= NULL
;
353 Context
*c
= userdata
;
356 if (isempty(c
->data
[PROP_ICON_NAME
]))
357 name
= n
= context_fallback_icon_name(c
);
359 name
= c
->data
[PROP_ICON_NAME
];
364 return sd_bus_message_append(reply
, "s", name
);
367 static int property_get_chassis(
370 const char *interface
,
371 const char *property
,
372 sd_bus_message
*reply
,
374 sd_bus_error
*error
) {
376 Context
*c
= userdata
;
379 if (isempty(c
->data
[PROP_CHASSIS
]))
380 name
= fallback_chassis();
382 name
= c
->data
[PROP_CHASSIS
];
384 return sd_bus_message_append(reply
, "s", name
);
387 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
388 Context
*c
= userdata
;
396 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
401 name
= c
->data
[PROP_STATIC_HOSTNAME
];
404 name
= FALLBACK_HOSTNAME
;
406 if (!hostname_is_valid(name
, false))
407 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
409 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
410 return sd_bus_reply_method_return(m
, NULL
);
412 r
= bus_verify_polkit_async(
415 "org.freedesktop.hostname1.set-hostname",
424 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
426 r
= free_and_strdup(&c
->data
[PROP_HOSTNAME
], name
);
430 r
= context_update_kernel_hostname(c
);
432 log_error_errno(r
, "Failed to set host name: %m");
433 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
436 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
438 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
440 return sd_bus_reply_method_return(m
, NULL
);
443 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
444 Context
*c
= userdata
;
452 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
456 name
= empty_to_null(name
);
458 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
459 return sd_bus_reply_method_return(m
, NULL
);
461 if (!isempty(name
) && !hostname_is_valid(name
, false))
462 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
464 r
= bus_verify_polkit_async(
467 "org.freedesktop.hostname1.set-static-hostname",
476 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
478 r
= free_and_strdup(&c
->data
[PROP_STATIC_HOSTNAME
], name
);
482 r
= context_update_kernel_hostname(c
);
484 log_error_errno(r
, "Failed to set host name: %m");
485 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
488 r
= context_write_data_static_hostname(c
);
490 log_error_errno(r
, "Failed to write static host name: %m");
491 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %m");
494 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
496 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
498 return sd_bus_reply_method_return(m
, NULL
);
501 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
509 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
513 name
= empty_to_null(name
);
515 if (streq_ptr(name
, c
->data
[prop
]))
516 return sd_bus_reply_method_return(m
, NULL
);
518 if (!isempty(name
)) {
519 /* The icon name might ultimately be used as file
520 * name, so better be safe than sorry */
522 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
523 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
524 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
525 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
526 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
527 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
528 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
529 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
530 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
531 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
534 /* Since the pretty hostname should always be changed at the
535 * same time as the static one, use the same policy action for
538 r
= bus_verify_polkit_async(
541 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
550 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
552 r
= free_and_strdup(&c
->data
[prop
], name
);
556 r
= context_write_data_machine_info(c
);
558 log_error_errno(r
, "Failed to write machine info: %m");
559 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %m");
562 log_info("Changed %s to '%s'",
563 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
564 prop
== PROP_DEPLOYMENT
? "deployment" :
565 prop
== PROP_LOCATION
? "location" :
566 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
568 (void) sd_bus_emit_properties_changed(
569 sd_bus_message_get_bus(m
),
570 "/org/freedesktop/hostname1",
571 "org.freedesktop.hostname1",
572 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
573 prop
== PROP_DEPLOYMENT
? "Deployment" :
574 prop
== PROP_LOCATION
? "Location" :
575 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
577 return sd_bus_reply_method_return(m
, NULL
);
580 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
581 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
584 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
585 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
588 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
589 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
592 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
593 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
596 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
597 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
600 static const sd_bus_vtable hostname_vtable
[] = {
601 SD_BUS_VTABLE_START(0),
602 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
603 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
604 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
605 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
606 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
607 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
608 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
609 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
610 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
611 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
612 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
613 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
614 SD_BUS_PROPERTY("HomeURL", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOME_URL
, SD_BUS_VTABLE_PROPERTY_CONST
),
615 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
616 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
617 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
618 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
619 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
620 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
621 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
625 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
626 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
633 r
= sd_bus_default_system(&bus
);
635 return log_error_errno(r
, "Failed to get system bus connection: %m");
637 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
639 return log_error_errno(r
, "Failed to register object: %m");
641 r
= sd_bus_request_name_async(bus
, NULL
, "org.freedesktop.hostname1", 0, NULL
, NULL
);
643 return log_error_errno(r
, "Failed to request name: %m");
645 r
= sd_bus_attach_event(bus
, event
, 0);
647 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
649 *_bus
= TAKE_PTR(bus
);
654 int main(int argc
, char *argv
[]) {
655 Context context
= {};
656 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
657 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
660 log_set_target(LOG_TARGET_AUTO
);
661 log_parse_environment();
668 log_error("This program takes no arguments.");
673 r
= sd_event_default(&event
);
675 log_error_errno(r
, "Failed to allocate event loop: %m");
679 sd_event_set_watchdog(event
, true);
681 r
= connect_bus(&context
, event
, &bus
);
685 r
= context_read_data(&context
);
687 log_error_errno(r
, "Failed to read hostname and machine information: %m");
691 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
693 log_error_errno(r
, "Failed to run event loop: %m");
698 context_free(&context
);
700 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;