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(
145 static bool valid_deployment(const char *deployment
) {
148 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
151 static const char* fallback_chassis(void) {
156 v
= detect_virtualization();
157 if (VIRTUALIZATION_IS_VM(v
))
159 if (VIRTUALIZATION_IS_CONTAINER(v
))
162 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
166 r
= safe_atou(type
, &t
);
171 /* We only list the really obvious cases here. The DMI data is unreliable enough, so let's not do any
172 additional guesswork on top of that.
174 See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here:
176 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf
181 case 0x3: /* Desktop */
182 case 0x4: /* Low Profile Desktop */
183 case 0x6: /* Mini Tower */
184 case 0x7: /* Tower */
187 case 0x8: /* Portable */
188 case 0x9: /* Laptop */
189 case 0xA: /* Notebook */
190 case 0xE: /* Sub Notebook */
193 case 0xB: /* Hand Held */
196 case 0x11: /* Main Server Chassis */
197 case 0x1C: /* Blade */
198 case 0x1D: /* Blade Enclosure */
201 case 0x1E: /* Tablet */
204 case 0x1F: /* Convertible */
205 case 0x20: /* Detachable */
206 return "convertible";
210 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
214 r
= safe_atou(type
, &t
);
219 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
221 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
223 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
228 case 1: /* Desktop */
229 case 3: /* Workstation */
230 case 6: /* Appliance PC */
236 case 4: /* Enterprise Server */
237 case 5: /* SOHO Server */
238 case 7: /* Performance Server */
248 static char* context_fallback_icon_name(Context
*c
) {
253 if (!isempty(c
->data
[PROP_CHASSIS
]))
254 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
256 chassis
= fallback_chassis();
258 return strappend("computer-", chassis
);
260 return strdup("computer");
264 static bool hostname_is_useful(const char *hn
) {
265 return !isempty(hn
) && !is_localhost(hn
);
268 static int context_update_kernel_hostname(Context
*c
) {
269 const char *static_hn
;
274 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
276 /* /etc/hostname with something other than "localhost"
277 * has the highest preference ... */
278 if (hostname_is_useful(static_hn
))
281 /* ... the transient host name, (ie: DHCP) comes next ... */
282 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
283 hn
= c
->data
[PROP_HOSTNAME
];
285 /* ... fallback to static "localhost.*" ignored above ... */
286 else if (!isempty(static_hn
))
289 /* ... and the ultimate fallback */
291 hn
= FALLBACK_HOSTNAME
;
293 if (sethostname_idempotent(hn
) < 0)
299 static int context_write_data_static_hostname(Context
*c
) {
303 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
305 if (unlink("/etc/hostname") < 0)
306 return errno
== ENOENT
? 0 : -errno
;
310 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
313 static int context_write_data_machine_info(Context
*c
) {
315 static const char * const name
[_PROP_MAX
] = {
316 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
317 [PROP_ICON_NAME
] = "ICON_NAME",
318 [PROP_CHASSIS
] = "CHASSIS",
319 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
320 [PROP_LOCATION
] = "LOCATION",
323 _cleanup_strv_free_
char **l
= NULL
;
328 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
329 if (r
< 0 && r
!= -ENOENT
)
332 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
333 _cleanup_free_
char *t
= NULL
;
338 if (isempty(c
->data
[p
])) {
339 strv_env_unset(l
, name
[p
]);
343 t
= strjoin(name
[p
], "=", c
->data
[p
]);
347 u
= strv_env_set(l
, t
);
355 if (strv_isempty(l
)) {
356 if (unlink("/etc/machine-info") < 0)
357 return errno
== ENOENT
? 0 : -errno
;
362 return write_env_file_label("/etc/machine-info", l
);
365 static int property_get_icon_name(
368 const char *interface
,
369 const char *property
,
370 sd_bus_message
*reply
,
372 sd_bus_error
*error
) {
374 _cleanup_free_
char *n
= NULL
;
375 Context
*c
= userdata
;
378 if (isempty(c
->data
[PROP_ICON_NAME
]))
379 name
= n
= context_fallback_icon_name(c
);
381 name
= c
->data
[PROP_ICON_NAME
];
386 return sd_bus_message_append(reply
, "s", name
);
389 static int property_get_chassis(
392 const char *interface
,
393 const char *property
,
394 sd_bus_message
*reply
,
396 sd_bus_error
*error
) {
398 Context
*c
= userdata
;
401 if (isempty(c
->data
[PROP_CHASSIS
]))
402 name
= fallback_chassis();
404 name
= c
->data
[PROP_CHASSIS
];
406 return sd_bus_message_append(reply
, "s", name
);
409 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
410 Context
*c
= userdata
;
419 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
424 name
= c
->data
[PROP_STATIC_HOSTNAME
];
427 name
= FALLBACK_HOSTNAME
;
429 if (!hostname_is_valid(name
, false))
430 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
432 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
433 return sd_bus_reply_method_return(m
, NULL
);
435 r
= bus_verify_polkit_async(
438 "org.freedesktop.hostname1.set-hostname",
447 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
453 free(c
->data
[PROP_HOSTNAME
]);
454 c
->data
[PROP_HOSTNAME
] = h
;
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 r
= bus_verify_polkit_async(
490 "org.freedesktop.hostname1.set-static-hostname",
499 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
502 c
->data
[PROP_STATIC_HOSTNAME
] = mfree(c
->data
[PROP_STATIC_HOSTNAME
]);
506 if (!hostname_is_valid(name
, false))
507 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
513 free(c
->data
[PROP_STATIC_HOSTNAME
]);
514 c
->data
[PROP_STATIC_HOSTNAME
] = h
;
517 r
= context_update_kernel_hostname(c
);
519 log_error_errno(r
, "Failed to set host name: %m");
520 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
523 r
= context_write_data_static_hostname(c
);
525 log_error_errno(r
, "Failed to write static host name: %m");
526 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %m");
529 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
531 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
533 return sd_bus_reply_method_return(m
, NULL
);
536 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
544 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
548 name
= empty_to_null(name
);
550 if (streq_ptr(name
, c
->data
[prop
]))
551 return sd_bus_reply_method_return(m
, NULL
);
553 /* Since the pretty hostname should always be changed at the
554 * same time as the static one, use the same policy action for
557 r
= bus_verify_polkit_async(
560 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
569 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
572 c
->data
[prop
] = mfree(c
->data
[prop
]);
576 /* The icon name might ultimately be used as file
577 * name, so better be safe than sorry */
579 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
580 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
581 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
582 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
583 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
584 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
585 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
586 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
587 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
588 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
598 r
= context_write_data_machine_info(c
);
600 log_error_errno(r
, "Failed to write machine info: %m");
601 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %m");
604 log_info("Changed %s to '%s'",
605 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
606 prop
== PROP_DEPLOYMENT
? "deployment" :
607 prop
== PROP_LOCATION
? "location" :
608 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
610 (void) sd_bus_emit_properties_changed(
611 sd_bus_message_get_bus(m
),
612 "/org/freedesktop/hostname1",
613 "org.freedesktop.hostname1",
614 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
615 prop
== PROP_DEPLOYMENT
? "Deployment" :
616 prop
== PROP_LOCATION
? "Location" :
617 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
619 return sd_bus_reply_method_return(m
, NULL
);
622 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
623 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
626 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
627 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
630 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
631 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
634 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
635 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
638 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
639 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
642 static const sd_bus_vtable hostname_vtable
[] = {
643 SD_BUS_VTABLE_START(0),
644 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
645 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
646 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
647 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
648 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
649 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
650 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
651 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
652 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
653 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
654 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
655 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
656 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
657 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
658 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
659 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
660 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
661 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
662 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
666 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
667 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
674 r
= sd_bus_default_system(&bus
);
676 return log_error_errno(r
, "Failed to get system bus connection: %m");
678 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
680 return log_error_errno(r
, "Failed to register object: %m");
682 r
= sd_bus_request_name(bus
, "org.freedesktop.hostname1", 0);
684 return log_error_errno(r
, "Failed to register name: %m");
686 r
= sd_bus_attach_event(bus
, event
, 0);
688 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
696 int main(int argc
, char *argv
[]) {
697 Context context
= {};
698 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
699 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
702 log_set_target(LOG_TARGET_AUTO
);
703 log_parse_environment();
710 log_error("This program takes no arguments.");
715 r
= sd_event_default(&event
);
717 log_error_errno(r
, "Failed to allocate event loop: %m");
721 sd_event_set_watchdog(event
, true);
723 r
= connect_bus(&context
, event
, &bus
);
727 r
= context_read_data(&context
);
729 log_error_errno(r
, "Failed to read hostname and machine information: %m");
733 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
735 log_error_errno(r
, "Failed to run event loop: %m");
740 context_free(&context
);
742 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;