2 This file is part of systemd.
4 Copyright 2011 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <sys/utsname.h>
25 #include "alloc-util.h"
29 #include "fileio-label.h"
30 #include "hostname-util.h"
31 #include "parse-util.h"
32 #include "path-util.h"
33 #include "selinux-util.h"
35 #include "user-util.h"
39 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
57 typedef struct Context
{
58 char *data
[_PROP_MAX
];
59 Hashmap
*polkit_registry
;
62 static void context_reset(Context
*c
) {
67 for (p
= 0; p
< _PROP_MAX
; p
++)
68 c
->data
[p
] = mfree(c
->data
[p
]);
71 static void context_free(Context
*c
) {
75 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
78 static int context_read_data(Context
*c
) {
86 assert_se(uname(&u
) >= 0);
87 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
88 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
89 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
90 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
91 !c
->data
[PROP_KERNEL_VERSION
])
94 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
95 if (!c
->data
[PROP_HOSTNAME
])
98 r
= read_hostname_config("/etc/hostname", &c
->data
[PROP_STATIC_HOSTNAME
]);
99 if (r
< 0 && r
!= -ENOENT
)
102 r
= parse_env_file("/etc/machine-info", NEWLINE
,
103 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
104 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
105 "CHASSIS", &c
->data
[PROP_CHASSIS
],
106 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
107 "LOCATION", &c
->data
[PROP_LOCATION
],
109 if (r
< 0 && r
!= -ENOENT
)
112 r
= parse_env_file("/etc/os-release", NEWLINE
,
113 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
114 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
117 r
= parse_env_file("/usr/lib/os-release", NEWLINE
,
118 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
119 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
122 if (r
< 0 && r
!= -ENOENT
)
128 static bool valid_chassis(const char *chassis
) {
131 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 */
205 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
209 r
= safe_atou(type
, &t
);
214 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
216 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
218 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
223 case 1: /* Desktop */
224 case 3: /* Workstation */
225 case 6: /* Appliance PC */
231 case 4: /* Enterprise Server */
232 case 5: /* SOHO Server */
233 case 7: /* Performance Server */
243 static char* context_fallback_icon_name(Context
*c
) {
248 if (!isempty(c
->data
[PROP_CHASSIS
]))
249 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
251 chassis
= fallback_chassis();
253 return strappend("computer-", chassis
);
255 return strdup("computer");
259 static bool hostname_is_useful(const char *hn
) {
260 return !isempty(hn
) && !is_localhost(hn
);
263 static int context_update_kernel_hostname(Context
*c
) {
264 const char *static_hn
;
269 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
271 /* /etc/hostname with something other than "localhost"
272 * has the highest preference ... */
273 if (hostname_is_useful(static_hn
))
276 /* ... the transient host name, (ie: DHCP) comes next ... */
277 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
278 hn
= c
->data
[PROP_HOSTNAME
];
280 /* ... fallback to static "localhost.*" ignored above ... */
281 else if (!isempty(static_hn
))
284 /* ... and the ultimate fallback */
288 if (sethostname_idempotent(hn
) < 0)
294 static int context_write_data_static_hostname(Context
*c
) {
298 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
300 if (unlink("/etc/hostname") < 0)
301 return errno
== ENOENT
? 0 : -errno
;
305 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
308 static int context_write_data_machine_info(Context
*c
) {
310 static const char * const name
[_PROP_MAX
] = {
311 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
312 [PROP_ICON_NAME
] = "ICON_NAME",
313 [PROP_CHASSIS
] = "CHASSIS",
314 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
315 [PROP_LOCATION
] = "LOCATION",
318 _cleanup_strv_free_
char **l
= NULL
;
323 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
324 if (r
< 0 && r
!= -ENOENT
)
327 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
328 _cleanup_free_
char *t
= NULL
;
333 if (isempty(c
->data
[p
])) {
334 strv_env_unset(l
, name
[p
]);
338 t
= strjoin(name
[p
], "=", c
->data
[p
], NULL
);
342 u
= strv_env_set(l
, t
);
350 if (strv_isempty(l
)) {
351 if (unlink("/etc/machine-info") < 0)
352 return errno
== ENOENT
? 0 : -errno
;
357 return write_env_file_label("/etc/machine-info", l
);
360 static int property_get_icon_name(
363 const char *interface
,
364 const char *property
,
365 sd_bus_message
*reply
,
367 sd_bus_error
*error
) {
369 _cleanup_free_
char *n
= NULL
;
370 Context
*c
= userdata
;
373 if (isempty(c
->data
[PROP_ICON_NAME
]))
374 name
= n
= context_fallback_icon_name(c
);
376 name
= c
->data
[PROP_ICON_NAME
];
381 return sd_bus_message_append(reply
, "s", name
);
384 static int property_get_chassis(
387 const char *interface
,
388 const char *property
,
389 sd_bus_message
*reply
,
391 sd_bus_error
*error
) {
393 Context
*c
= userdata
;
396 if (isempty(c
->data
[PROP_CHASSIS
]))
397 name
= fallback_chassis();
399 name
= c
->data
[PROP_CHASSIS
];
401 return sd_bus_message_append(reply
, "s", name
);
404 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
405 Context
*c
= userdata
;
414 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
419 name
= c
->data
[PROP_STATIC_HOSTNAME
];
424 if (!hostname_is_valid(name
, false))
425 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
427 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
428 return sd_bus_reply_method_return(m
, NULL
);
430 r
= bus_verify_polkit_async(
433 "org.freedesktop.hostname1.set-hostname",
442 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
448 free(c
->data
[PROP_HOSTNAME
]);
449 c
->data
[PROP_HOSTNAME
] = h
;
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: %s", strerror(-r
));
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 r
= bus_verify_polkit_async(
485 "org.freedesktop.hostname1.set-static-hostname",
494 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
497 c
->data
[PROP_STATIC_HOSTNAME
] = mfree(c
->data
[PROP_STATIC_HOSTNAME
]);
501 if (!hostname_is_valid(name
, false))
502 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
508 free(c
->data
[PROP_STATIC_HOSTNAME
]);
509 c
->data
[PROP_STATIC_HOSTNAME
] = h
;
512 r
= context_update_kernel_hostname(c
);
514 log_error_errno(r
, "Failed to set host name: %m");
515 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
518 r
= context_write_data_static_hostname(c
);
520 log_error_errno(r
, "Failed to write static host name: %m");
521 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %s", strerror(-r
));
524 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
526 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
528 return sd_bus_reply_method_return(m
, NULL
);
531 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
539 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
543 name
= empty_to_null(name
);
545 if (streq_ptr(name
, c
->data
[prop
]))
546 return sd_bus_reply_method_return(m
, NULL
);
548 /* Since the pretty hostname should always be changed at the
549 * same time as the static one, use the same policy action for
552 r
= bus_verify_polkit_async(
555 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
564 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
567 c
->data
[prop
] = mfree(c
->data
[prop
]);
571 /* The icon name might ultimately be used as file
572 * name, so better be safe than sorry */
574 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
575 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
576 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
577 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
578 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
579 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
580 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
581 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
582 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
583 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
593 r
= context_write_data_machine_info(c
);
595 log_error_errno(r
, "Failed to write machine info: %m");
596 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %s", strerror(-r
));
599 log_info("Changed %s to '%s'",
600 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
601 prop
== PROP_DEPLOYMENT
? "deployment" :
602 prop
== PROP_LOCATION
? "location" :
603 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
605 (void) sd_bus_emit_properties_changed(
606 sd_bus_message_get_bus(m
),
607 "/org/freedesktop/hostname1",
608 "org.freedesktop.hostname1",
609 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
610 prop
== PROP_DEPLOYMENT
? "Deployment" :
611 prop
== PROP_LOCATION
? "Location" :
612 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
614 return sd_bus_reply_method_return(m
, NULL
);
617 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
618 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
621 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
622 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
625 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
626 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
629 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
630 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
633 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
634 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
637 static const sd_bus_vtable hostname_vtable
[] = {
638 SD_BUS_VTABLE_START(0),
639 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
640 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
641 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
642 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
643 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
644 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
645 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
646 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
647 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
648 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
649 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
650 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
651 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
652 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
653 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
654 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
655 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
656 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
657 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
661 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
662 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
669 r
= sd_bus_default_system(&bus
);
671 return log_error_errno(r
, "Failed to get system bus connection: %m");
673 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
675 return log_error_errno(r
, "Failed to register object: %m");
677 r
= sd_bus_request_name(bus
, "org.freedesktop.hostname1", 0);
679 return log_error_errno(r
, "Failed to register name: %m");
681 r
= sd_bus_attach_event(bus
, event
, 0);
683 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
691 int main(int argc
, char *argv
[]) {
692 Context context
= {};
693 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
694 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
697 log_set_target(LOG_TARGET_AUTO
);
698 log_parse_environment();
705 log_error("This program takes no arguments.");
710 r
= sd_event_default(&event
);
712 log_error_errno(r
, "Failed to allocate event loop: %m");
716 sd_event_set_watchdog(event
, true);
718 r
= connect_bus(&context
, event
, &bus
);
722 r
= context_read_data(&context
);
724 log_error_errno(r
, "Failed to read hostname and machine information: %m");
728 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
730 log_error_errno(r
, "Failed to run event loop: %m");
735 context_free(&context
);
737 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;