1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include <sys/utsname.h>
32 #include "fileio-label.h"
35 #include "event-util.h"
36 #include "selinux-util.h"
38 #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:")
56 typedef struct Context
{
57 char *data
[_PROP_MAX
];
58 Hashmap
*polkit_registry
;
61 static void context_reset(Context
*c
) {
66 for (p
= 0; p
< _PROP_MAX
; p
++) {
72 static void context_free(Context
*c
) {
76 bus_verify_polkit_async_registry_free(c
->polkit_registry
);
79 static int context_read_data(Context
*c
) {
87 assert_se(uname(&u
) >= 0);
88 c
->data
[PROP_KERNEL_NAME
] = strdup(u
.sysname
);
89 c
->data
[PROP_KERNEL_RELEASE
] = strdup(u
.release
);
90 c
->data
[PROP_KERNEL_VERSION
] = strdup(u
.version
);
91 if (!c
->data
[PROP_KERNEL_NAME
] || !c
->data
[PROP_KERNEL_RELEASE
] ||
92 !c
->data
[PROP_KERNEL_VERSION
])
95 c
->data
[PROP_HOSTNAME
] = gethostname_malloc();
96 if (!c
->data
[PROP_HOSTNAME
])
99 r
= read_one_line_file("/etc/hostname", &c
->data
[PROP_STATIC_HOSTNAME
]);
100 if (r
< 0 && r
!= -ENOENT
)
103 r
= parse_env_file("/etc/machine-info", NEWLINE
,
104 "PRETTY_HOSTNAME", &c
->data
[PROP_PRETTY_HOSTNAME
],
105 "ICON_NAME", &c
->data
[PROP_ICON_NAME
],
106 "CHASSIS", &c
->data
[PROP_CHASSIS
],
107 "DEPLOYMENT", &c
->data
[PROP_DEPLOYMENT
],
108 "LOCATION", &c
->data
[PROP_LOCATION
],
110 if (r
< 0 && r
!= -ENOENT
)
113 r
= parse_env_file("/etc/os-release", NEWLINE
,
114 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
115 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
118 r
= parse_env_file("/usr/lib/os-release", NEWLINE
,
119 "PRETTY_NAME", &c
->data
[PROP_OS_PRETTY_NAME
],
120 "CPE_NAME", &c
->data
[PROP_OS_CPE_NAME
],
124 if (r
< 0 && r
!= -ENOENT
)
130 static bool valid_chassis(const char *chassis
) {
133 return nulstr_contains(
146 static bool valid_deployment(const char *deployment
) {
149 return in_charset(deployment
, VALID_DEPLOYMENT_CHARS
);
152 static const char* fallback_chassis(void) {
158 v
= detect_virtualization(NULL
);
160 if (v
== VIRTUALIZATION_VM
)
162 if (v
== VIRTUALIZATION_CONTAINER
)
165 r
= read_one_line_file("/sys/firmware/acpi/pm_profile", &type
);
169 r
= safe_atou(type
, &t
);
174 /* We only list the really obvious cases here as the ACPI data
175 * is not really super reliable.
177 * See the ACPI 5.0 Spec Section 5.2.9.1 for details:
179 * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf
202 r
= read_one_line_file("/sys/class/dmi/id/chassis_type", &type
);
206 r
= safe_atou(type
, &t
);
211 /* We only list the really obvious cases here. The DMI data is
212 unreliable enough, so let's not do any additional guesswork
215 See the SMBIOS Specification 2.7.1 section 7.4.1 for
216 details about the values listed here:
218 http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
246 static char* context_fallback_icon_name(Context
*c
) {
251 if (!isempty(c
->data
[PROP_CHASSIS
]))
252 return strappend("computer-", c
->data
[PROP_CHASSIS
]);
254 chassis
= fallback_chassis();
256 return strappend("computer-", chassis
);
258 return strdup("computer");
262 static bool hostname_is_useful(const char *hn
) {
263 return !isempty(hn
) && !is_localhost(hn
);
266 static int context_update_kernel_hostname(Context
*c
) {
267 const char *static_hn
;
272 static_hn
= c
->data
[PROP_STATIC_HOSTNAME
];
274 /* /etc/hostname with something other than "localhost"
275 * has the highest preference ... */
276 if (hostname_is_useful(static_hn
))
279 /* ... the transient host name, (ie: DHCP) comes next ... */
280 else if (!isempty(c
->data
[PROP_HOSTNAME
]))
281 hn
= c
->data
[PROP_HOSTNAME
];
283 /* ... fallback to static "localhost.*" ignored above ... */
284 else if (!isempty(static_hn
))
287 /* ... and the ultimate fallback */
291 if (sethostname_idempotent(hn
) < 0)
297 static int context_write_data_static_hostname(Context
*c
) {
301 if (isempty(c
->data
[PROP_STATIC_HOSTNAME
])) {
303 if (unlink("/etc/hostname") < 0)
304 return errno
== ENOENT
? 0 : -errno
;
308 return write_string_file_atomic_label("/etc/hostname", c
->data
[PROP_STATIC_HOSTNAME
]);
311 static int context_write_data_machine_info(Context
*c
) {
313 static const char * const name
[_PROP_MAX
] = {
314 [PROP_PRETTY_HOSTNAME
] = "PRETTY_HOSTNAME",
315 [PROP_ICON_NAME
] = "ICON_NAME",
316 [PROP_CHASSIS
] = "CHASSIS",
317 [PROP_DEPLOYMENT
] = "DEPLOYMENT",
318 [PROP_LOCATION
] = "LOCATION",
321 _cleanup_strv_free_
char **l
= NULL
;
326 r
= load_env_file(NULL
, "/etc/machine-info", NULL
, &l
);
327 if (r
< 0 && r
!= -ENOENT
)
330 for (p
= PROP_PRETTY_HOSTNAME
; p
<= PROP_LOCATION
; p
++) {
331 _cleanup_free_
char *t
= NULL
;
336 if (isempty(c
->data
[p
])) {
337 strv_env_unset(l
, name
[p
]);
341 t
= strjoin(name
[p
], "=", c
->data
[p
], NULL
);
345 u
= strv_env_set(l
, t
);
353 if (strv_isempty(l
)) {
354 if (unlink("/etc/machine-info") < 0)
355 return errno
== ENOENT
? 0 : -errno
;
360 return write_env_file_label("/etc/machine-info", l
);
363 static int property_get_icon_name(
366 const char *interface
,
367 const char *property
,
368 sd_bus_message
*reply
,
370 sd_bus_error
*error
) {
372 _cleanup_free_
char *n
= NULL
;
373 Context
*c
= userdata
;
376 if (isempty(c
->data
[PROP_ICON_NAME
]))
377 name
= n
= context_fallback_icon_name(c
);
379 name
= c
->data
[PROP_ICON_NAME
];
384 return sd_bus_message_append(reply
, "s", name
);
387 static int property_get_chassis(
390 const char *interface
,
391 const char *property
,
392 sd_bus_message
*reply
,
394 sd_bus_error
*error
) {
396 Context
*c
= userdata
;
399 if (isempty(c
->data
[PROP_CHASSIS
]))
400 name
= fallback_chassis();
402 name
= c
->data
[PROP_CHASSIS
];
404 return sd_bus_message_append(reply
, "s", name
);
407 static int method_set_hostname(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
408 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
))
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(m
, CAP_SYS_ADMIN
, "org.freedesktop.hostname1.set-hostname", interactive
, &c
->polkit_registry
, error
);
434 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
440 free(c
->data
[PROP_HOSTNAME
]);
441 c
->data
[PROP_HOSTNAME
] = h
;
443 r
= context_update_kernel_hostname(c
);
445 log_error_errno(r
, "Failed to set host name: %m");
446 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
449 log_info("Changed host name to '%s'", strna(c
->data
[PROP_HOSTNAME
]));
451 sd_bus_emit_properties_changed(bus
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL
);
453 return sd_bus_reply_method_return(m
, NULL
);
456 static int method_set_static_hostname(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
457 Context
*c
= userdata
;
462 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
469 if (streq_ptr(name
, c
->data
[PROP_STATIC_HOSTNAME
]))
470 return sd_bus_reply_method_return(m
, NULL
);
472 r
= bus_verify_polkit_async(m
, CAP_SYS_ADMIN
, "org.freedesktop.hostname1.set-static-hostname", interactive
, &c
->polkit_registry
, error
);
476 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
479 free(c
->data
[PROP_STATIC_HOSTNAME
]);
480 c
->data
[PROP_STATIC_HOSTNAME
] = NULL
;
484 if (!hostname_is_valid(name
))
485 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid static hostname '%s'", name
);
491 free(c
->data
[PROP_STATIC_HOSTNAME
]);
492 c
->data
[PROP_STATIC_HOSTNAME
] = h
;
495 r
= context_update_kernel_hostname(c
);
497 log_error_errno(r
, "Failed to set host name: %m");
498 return sd_bus_error_set_errnof(error
, r
, "Failed to set hostname: %s", strerror(-r
));
501 r
= context_write_data_static_hostname(c
);
503 log_error_errno(r
, "Failed to write static host name: %m");
504 return sd_bus_error_set_errnof(error
, r
, "Failed to set static hostname: %s", strerror(-r
));
507 log_info("Changed static host name to '%s'", strna(c
->data
[PROP_STATIC_HOSTNAME
]));
509 sd_bus_emit_properties_changed(bus
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL
);
511 return sd_bus_reply_method_return(m
, NULL
);
514 static int set_machine_info(Context
*c
, sd_bus
*bus
, sd_bus_message
*m
, int prop
, sd_bus_message_handler_t cb
, sd_bus_error
*error
) {
523 r
= sd_bus_message_read(m
, "sb", &name
, &interactive
);
530 if (streq_ptr(name
, c
->data
[prop
]))
531 return sd_bus_reply_method_return(m
, NULL
);
533 /* Since the pretty hostname should always be changed at the
534 * same time as the static one, use the same policy action for
537 r
= bus_verify_polkit_async(m
, CAP_SYS_ADMIN
,
538 prop
== PROP_PRETTY_HOSTNAME
?
539 "org.freedesktop.hostname1.set-static-hostname" :
540 "org.freedesktop.hostname1.set-machine-info", interactive
, &c
->polkit_registry
, error
);
544 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
548 c
->data
[prop
] = NULL
;
552 /* The icon name might ultimately be used as file
553 * name, so better be safe than sorry */
555 if (prop
== PROP_ICON_NAME
&& !filename_is_valid(name
))
556 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid icon name '%s'", name
);
557 if (prop
== PROP_PRETTY_HOSTNAME
&& string_has_cc(name
, NULL
))
558 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid pretty host name '%s'", name
);
559 if (prop
== PROP_CHASSIS
&& !valid_chassis(name
))
560 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid chassis '%s'", name
);
561 if (prop
== PROP_DEPLOYMENT
&& !valid_deployment(name
))
562 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid deployment '%s'", name
);
563 if (prop
== PROP_LOCATION
&& string_has_cc(name
, NULL
))
564 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Invalid location '%s'", name
);
574 r
= context_write_data_machine_info(c
);
576 log_error_errno(r
, "Failed to write machine info: %m");
577 return sd_bus_error_set_errnof(error
, r
, "Failed to write machine info: %s", strerror(-r
));
580 log_info("Changed %s to '%s'",
581 prop
== PROP_PRETTY_HOSTNAME
? "pretty host name" :
582 prop
== PROP_DEPLOYMENT
? "deployment" :
583 prop
== PROP_LOCATION
? "location" :
584 prop
== PROP_CHASSIS
? "chassis" : "icon name", strna(c
->data
[prop
]));
586 sd_bus_emit_properties_changed(bus
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1",
587 prop
== PROP_PRETTY_HOSTNAME
? "PrettyHostname" :
588 prop
== PROP_DEPLOYMENT
? "Deployment" :
589 prop
== PROP_LOCATION
? "Location" :
590 prop
== PROP_CHASSIS
? "Chassis" : "IconName" , NULL
);
592 return sd_bus_reply_method_return(m
, NULL
);
595 static int method_set_pretty_hostname(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
596 return set_machine_info(userdata
, bus
, m
, PROP_PRETTY_HOSTNAME
, method_set_pretty_hostname
, error
);
599 static int method_set_icon_name(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
600 return set_machine_info(userdata
, bus
, m
, PROP_ICON_NAME
, method_set_icon_name
, error
);
603 static int method_set_chassis(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
604 return set_machine_info(userdata
, bus
, m
, PROP_CHASSIS
, method_set_chassis
, error
);
607 static int method_set_deployment(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
608 return set_machine_info(userdata
, bus
, m
, PROP_DEPLOYMENT
, method_set_deployment
, error
);
611 static int method_set_location(sd_bus
*bus
, sd_bus_message
*m
, void *userdata
, sd_bus_error
*error
) {
612 return set_machine_info(userdata
, bus
, m
, PROP_LOCATION
, method_set_location
, error
);
615 static const sd_bus_vtable hostname_vtable
[] = {
616 SD_BUS_VTABLE_START(0),
617 SD_BUS_PROPERTY("Hostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
618 SD_BUS_PROPERTY("StaticHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_STATIC_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
619 SD_BUS_PROPERTY("PrettyHostname", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_PRETTY_HOSTNAME
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
620 SD_BUS_PROPERTY("IconName", "s", property_get_icon_name
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
621 SD_BUS_PROPERTY("Chassis", "s", property_get_chassis
, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
622 SD_BUS_PROPERTY("Deployment", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_DEPLOYMENT
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
623 SD_BUS_PROPERTY("Location", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_LOCATION
, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
),
624 SD_BUS_PROPERTY("KernelName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
625 SD_BUS_PROPERTY("KernelRelease", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_RELEASE
, SD_BUS_VTABLE_PROPERTY_CONST
),
626 SD_BUS_PROPERTY("KernelVersion", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_KERNEL_VERSION
, SD_BUS_VTABLE_PROPERTY_CONST
),
627 SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_PRETTY_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
628 SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL
, offsetof(Context
, data
) + sizeof(char*) * PROP_OS_CPE_NAME
, SD_BUS_VTABLE_PROPERTY_CONST
),
629 SD_BUS_METHOD("SetHostname", "sb", NULL
, method_set_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
630 SD_BUS_METHOD("SetStaticHostname", "sb", NULL
, method_set_static_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
631 SD_BUS_METHOD("SetPrettyHostname", "sb", NULL
, method_set_pretty_hostname
, SD_BUS_VTABLE_UNPRIVILEGED
),
632 SD_BUS_METHOD("SetIconName", "sb", NULL
, method_set_icon_name
, SD_BUS_VTABLE_UNPRIVILEGED
),
633 SD_BUS_METHOD("SetChassis", "sb", NULL
, method_set_chassis
, SD_BUS_VTABLE_UNPRIVILEGED
),
634 SD_BUS_METHOD("SetDeployment", "sb", NULL
, method_set_deployment
, SD_BUS_VTABLE_UNPRIVILEGED
),
635 SD_BUS_METHOD("SetLocation", "sb", NULL
, method_set_location
, SD_BUS_VTABLE_UNPRIVILEGED
),
639 static int connect_bus(Context
*c
, sd_event
*event
, sd_bus
**_bus
) {
640 _cleanup_bus_close_unref_ sd_bus
*bus
= NULL
;
647 r
= sd_bus_default_system(&bus
);
649 return log_error_errno(r
, "Failed to get system bus connection: %m");
651 r
= sd_bus_add_object_vtable(bus
, NULL
, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable
, c
);
653 return log_error_errno(r
, "Failed to register object: %m");
655 r
= sd_bus_request_name(bus
, "org.freedesktop.hostname1", 0);
657 return log_error_errno(r
, "Failed to register name: %m");
659 r
= sd_bus_attach_event(bus
, event
, 0);
661 return log_error_errno(r
, "Failed to attach bus to event loop: %m");
669 int main(int argc
, char *argv
[]) {
670 Context context
= {};
671 _cleanup_event_unref_ sd_event
*event
= NULL
;
672 _cleanup_bus_close_unref_ sd_bus
*bus
= NULL
;
675 log_set_target(LOG_TARGET_AUTO
);
676 log_parse_environment();
680 mac_selinux_init("/etc");
683 log_error("This program takes no arguments.");
689 log_error("This program takes no arguments.");
694 r
= sd_event_default(&event
);
696 log_error_errno(r
, "Failed to allocate event loop: %m");
700 sd_event_set_watchdog(event
, true);
702 r
= connect_bus(&context
, event
, &bus
);
706 r
= context_read_data(&context
);
708 log_error_errno(r
, "Failed to read hostname and machine information: %m");
712 r
= bus_event_loop_with_idle(event
, bus
, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC
, NULL
, NULL
);
714 log_error_errno(r
, "Failed to run event loop: %m");
719 context_free(&context
);
721 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;