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 return "convertible";
209 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
213 r
= safe_atou(type
, &t
);
218 /* We only list the really obvious cases here as the ACPI data is not really super reliable.
220 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
222 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
227 case 1: /* Desktop */
228 case 3: /* Workstation */
229 case 6: /* Appliance PC */
235 case 4: /* Enterprise Server */
236 case 5: /* SOHO Server */
237 case 7: /* Performance Server */
247 static char* context_fallback_icon_name(Context
*c
) {
252 if (!isempty(c
->data
[PROP_CHASSIS
]))
253 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
255 chassis
= fallback_chassis();
257 return strappend("computer-", chassis
);
259 return strdup("computer");
263 static bool hostname_is_useful(const char *hn
) {
264 return !isempty(hn
) && !is_localhost(hn
);
267 static int context_update_kernel_hostname(Context
*c
) {
268 const char *static_hn
;
273 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
275 /* /etc/hostname with something other than "localhost"
276 * has the highest preference ... */
277 if (hostname_is_useful(static_hn
))
280 /* ... the transient host name, (ie: DHCP) comes next ... */
281 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
282 hn
= c
->data
[PROP_HOSTNAME
];
284 /* ... fallback to static "localhost.*" ignored above ... */
285 else if (!isempty(static_hn
))
288 /* ... and the ultimate fallback */
290 hn
= FALLBACK_HOSTNAME
;
292 if (sethostname_idempotent(hn
) < 0)
298 static int context_write_data_static_hostname(Context
*c
) {
302 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
304 if (unlink("/etc/hostname") < 0)
305 return errno
== ENOENT
? 0 : -errno
;
309 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
312 static int context_write_data_machine_info(Context
*c
) {
314 static const char * const name
[_PROP_MAX
] = {
315 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
316 [PROP_ICON_NAME
] = "ICON_NAME",
317 [PROP_CHASSIS
] = "CHASSIS",
318 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
319 [PROP_LOCATION
] = "LOCATION",
322 _cleanup_strv_free_
char **l
= NULL
;
327 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
328 if (r
< 0 && r
!= -ENOENT
)
331 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
332 _cleanup_free_
char *t
= NULL
;
337 if (isempty(c
->data
[p
])) {
338 strv_env_unset(l
, name
[p
]);
342 t
= strjoin(name
[p
], "=", c
->data
[p
]);
346 u
= strv_env_set(l
, t
);
354 if (strv_isempty(l
)) {
355 if (unlink("/etc/machine-info") < 0)
356 return errno
== ENOENT
? 0 : -errno
;
361 return write_env_file_label("/etc/machine-info", l
);
364 static int property_get_icon_name(
367 const char *interface
,
368 const char *property
,
369 sd_bus_message
*reply
,
371 sd_bus_error
*error
) {
373 _cleanup_free_
char *n
= NULL
;
374 Context
*c
= userdata
;
377 if (isempty(c
->data
[PROP_ICON_NAME
]))
378 name
= n
= context_fallback_icon_name(c
);
380 name
= c
->data
[PROP_ICON_NAME
];
385 return sd_bus_message_append(reply
, "s", name
);
388 static int property_get_chassis(
391 const char *interface
,
392 const char *property
,
393 sd_bus_message
*reply
,
395 sd_bus_error
*error
) {
397 Context
*c
= userdata
;
400 if (isempty(c
->data
[PROP_CHASSIS
]))
401 name
= fallback_chassis();
403 name
= c
->data
[PROP_CHASSIS
];
405 return sd_bus_message_append(reply
, "s", name
);
408 static int method_set_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
409 Context
*c
= userdata
;
418 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
423 name
= c
->data
[PROP_STATIC_HOSTNAME
];
426 name
= FALLBACK_HOSTNAME
;
428 if (!hostname_is_valid(name
, false))
429 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid hostname '%s'", name
);
431 if (streq_ptr(name
, c
->data
[PROP_HOSTNAME
]))
432 return sd_bus_reply_method_return(m
, NULL
);
434 r
= bus_verify_polkit_async(
437 "org.freedesktop.hostname1.set-hostname",
446 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
452 free(c
->data
[PROP_HOSTNAME
]);
453 c
->data
[PROP_HOSTNAME
] = h
;
455 r
= context_update_kernel_hostname(c
);
457 log_error_errno(r
, "Failed to set host name: %m");
458 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
461 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
463 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
465 return sd_bus_reply_method_return(m
, NULL
);
468 static int method_set_static_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
469 Context
*c
= userdata
;
477 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
481 name
= empty_to_null(name
);
483 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
484 return sd_bus_reply_method_return(m
, NULL
);
486 r
= bus_verify_polkit_async(
489 "org.freedesktop.hostname1.set-static-hostname",
498 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
501 c
->data
[PROP_STATIC_HOSTNAME
] = mfree(c
->data
[PROP_STATIC_HOSTNAME
]);
505 if (!hostname_is_valid(name
, false))
506 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
512 free(c
->data
[PROP_STATIC_HOSTNAME
]);
513 c
->data
[PROP_STATIC_HOSTNAME
] = h
;
516 r
= context_update_kernel_hostname(c
);
518 log_error_errno(r
, "Failed to set host name: %m");
519 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %m");
522 r
= context_write_data_static_hostname(c
);
524 log_error_errno(r
, "Failed to write static host name: %m");
525 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %m");
528 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
530 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m
), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
532 return sd_bus_reply_method_return(m
, NULL
);
535 static int set_machine_info(Context
*c
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
543 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
547 name
= empty_to_null(name
);
549 if (streq_ptr(name
, c
->data
[prop
]))
550 return sd_bus_reply_method_return(m
, NULL
);
552 /* Since the pretty hostname should always be changed at the
553 * same time as the static one, use the same policy action for
556 r
= bus_verify_polkit_async(
559 prop
== PROP_PRETTY_HOSTNAME
? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info",
568 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
571 c
->data
[prop
] = mfree(c
->data
[prop
]);
575 /* The icon name might ultimately be used as file
576 * name, so better be safe than sorry */
578 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
579 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
580 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
581 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
582 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
583 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
584 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
585 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
586 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
587 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
597 r
= context_write_data_machine_info(c
);
599 log_error_errno(r
, "Failed to write machine info: %m");
600 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %m");
603 log_info("Changed %s to '%s'",
604 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
605 prop
== PROP_DEPLOYMENT
? "deployment" :
606 prop
== PROP_LOCATION
? "location" :
607 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
609 (void) sd_bus_emit_properties_changed(
610 sd_bus_message_get_bus(m
),
611 "/org/freedesktop/hostname1",
612 "org.freedesktop.hostname1",
613 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
614 prop
== PROP_DEPLOYMENT
? "Deployment" :
615 prop
== PROP_LOCATION
? "Location" :
616 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
618 return sd_bus_reply_method_return(m
, NULL
);
621 static int method_set_pretty_hostname(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
622 return set_machine_info(userdata
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
625 static int method_set_icon_name(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
626 return set_machine_info(userdata
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
629 static int method_set_chassis(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
630 return set_machine_info(userdata
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
633 static int method_set_deployment(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
634 return set_machine_info(userdata
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
637 static int method_set_location(sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
638 return set_machine_info(userdata
, m
, PROP_LOCATION
, method_set_location
, error
);
641 static const sd_bus_vtable hostname_vtable
[] = {
642 SD_BUS_VTABLE_START(0),
643 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
644 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
645 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
646 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
647 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
648 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
649 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
650 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
651 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
652 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
653 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
654 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
655 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
656 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
657 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
658 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
659 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
660 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
661 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
665 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
666 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
673 r
= sd_bus_default_system(&bus
);
675 return log_error_errno(r
, "Failed to get system bus connection: %m");
677 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
679 return log_error_errno(r
, "Failed to register object: %m");
681 r
= sd_bus_request_name(bus
, "org.freedesktop.hostname1", 0);
683 return log_error_errno(r
, "Failed to register name: %m");
685 r
= sd_bus_attach_event(bus
, event
, 0);
687 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
695 int main(int argc
, char *argv
[]) {
696 Context context
= {};
697 _cleanup_(sd_event_unrefp
) sd_event
*event
= NULL
;
698 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
701 log_set_target(LOG_TARGET_AUTO
);
702 log_parse_environment();
709 log_error("This program takes no arguments.");
714 r
= sd_event_default(&event
);
716 log_error_errno(r
, "Failed to allocate event loop: %m");
720 sd_event_set_watchdog(event
, true);
722 r
= connect_bus(&context
, event
, &bus
);
726 r
= context_read_data(&context
);
728 log_error_errno(r
, "Failed to read hostname and machine information: %m");
732 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
734 log_error_errno(r
, "Failed to run event loop: %m");
739 context_free(&context
);
741 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;