// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "lib/bluetooth.h" #include "lib/sdp.h" #include "gdbus/gdbus.h" #include "src/dbus-common.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/log.h" /* http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm */ #define MAJOR_CLASS_MISCELLANEOUS 0x00 #define MAJOR_CLASS_COMPUTER 0x01 #define MINOR_CLASS_UNCATEGORIZED 0x00 #define MINOR_CLASS_DESKTOP 0x01 #define MINOR_CLASS_SERVER 0x02 #define MINOR_CLASS_LAPTOP 0x03 #define MINOR_CLASS_HANDHELD 0x04 #define MINOR_CLASS_PALM_SIZED 0x05 #define MINOR_CLASS_WEARABLE 0x06 #define MINOR_CLASS_TABLET 0x07 static uint8_t major_class = MAJOR_CLASS_MISCELLANEOUS; static uint8_t minor_class = MINOR_CLASS_UNCATEGORIZED; static char *pretty_hostname = NULL; static char *static_hostname = NULL; static char *transient_hostname = NULL; static guint hostname_id = 0; /* * Fallback to static hostname only if empty pretty hostname was already * received. */ static const char *get_hostname(void) { if (pretty_hostname) { if (g_str_equal(pretty_hostname, "") == FALSE) return pretty_hostname; if (static_hostname && g_str_equal(static_hostname, "") == FALSE) return static_hostname; if (transient_hostname && g_str_equal(transient_hostname, "") == FALSE) return transient_hostname; } return NULL; } static void update_name(struct btd_adapter *adapter, gpointer user_data) { const char *hostname = get_hostname(); if (hostname == NULL) return; if (btd_adapter_is_default(adapter)) { DBG("name: %s", hostname); adapter_set_name(adapter, hostname); } else { uint16_t index = btd_adapter_get_index(adapter); char *str; /* Avoid "some device #0" names, start at #1 */ str = g_strdup_printf("%s #%u", hostname, index + 1); DBG("name: %s", str); adapter_set_name(adapter, str); g_free(str); } } static void update_class(struct btd_adapter *adapter, gpointer user_data) { if (major_class == MAJOR_CLASS_MISCELLANEOUS) return; DBG("major: 0x%02x minor: 0x%02x", major_class, minor_class); btd_adapter_set_class(adapter, major_class, minor_class); } static const struct { const char *chassis; uint8_t major_class; uint8_t minor_class; } chassis_table[] = { { "desktop", MAJOR_CLASS_COMPUTER, MINOR_CLASS_DESKTOP }, { "server", MAJOR_CLASS_COMPUTER, MINOR_CLASS_SERVER }, { "laptop", MAJOR_CLASS_COMPUTER, MINOR_CLASS_LAPTOP }, { "handset", MAJOR_CLASS_COMPUTER, MINOR_CLASS_HANDHELD }, { "tablet", MAJOR_CLASS_COMPUTER, MINOR_CLASS_TABLET }, { } }; static void property_changed(GDBusProxy *proxy, const char *name, DBusMessageIter *iter, void *user_data) { if (g_str_equal(name, "PrettyHostname") == TRUE) { if (iter == NULL) { g_dbus_proxy_refresh_property(proxy, name); return; } if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { const char *str; dbus_message_iter_get_basic(iter, &str); DBG("pretty hostname: '%s'", str); g_free(pretty_hostname); pretty_hostname = g_strdup(str); adapter_foreach(update_name, NULL); } } else if (g_str_equal(name, "StaticHostname") == TRUE) { if (iter == NULL) { g_dbus_proxy_refresh_property(proxy, name); return; } if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { const char *str; dbus_message_iter_get_basic(iter, &str); DBG("static hostname: '%s'", str); g_free(static_hostname); static_hostname = g_strdup(str); adapter_foreach(update_name, NULL); } } else if (g_str_equal(name, "Chassis") == TRUE) { if (iter == NULL) { g_dbus_proxy_refresh_property(proxy, name); return; } if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { const char *str; int i; dbus_message_iter_get_basic(iter, &str); DBG("chassis: '%s'", str); for (i = 0; chassis_table[i].chassis; i++) { if (strcmp(chassis_table[i].chassis, str)) continue; major_class = chassis_table[i].major_class; minor_class = chassis_table[i].minor_class; adapter_foreach(update_class, NULL); break; } } } } static void read_transient_hostname(void) { struct utsname u; if (uname(&u) != 0) { g_free(transient_hostname); transient_hostname = NULL; DBG("failed to read transient hostname"); return; } g_free(transient_hostname); transient_hostname = g_strdup(u.nodename); DBG("read transient hostname: '%s'", transient_hostname); } static gboolean hostname_cb(GIOChannel *io, GIOCondition cond, gpointer user_data) { DBG("transient hostname changed"); read_transient_hostname(); adapter_foreach(update_class, NULL); return TRUE; } static int hostname_probe(struct btd_adapter *adapter) { DBG(""); update_name(adapter, NULL); update_class(adapter, NULL); return 0; } static void hostname_remove(struct btd_adapter *adapter) { DBG(""); } static struct btd_adapter_driver hostname_driver = { .name = "hostname", .probe = hostname_probe, .remove = hostname_remove, }; static void read_dmi_fallback(void) { char *contents; int i, type; const char *str; if (g_file_get_contents("/sys/class/dmi/id/chassis_type", &contents, NULL, NULL) == FALSE) return; type = atoi(contents); g_free(contents); if (type < 0 || type > 0x1D) return; /* from systemd hostname chassis list */ switch (type) { case 0x3: case 0x4: case 0x6: case 0x7: str = "desktop"; break; case 0x8: case 0x9: case 0xA: case 0xE: str = "laptop"; break; case 0xB: str = "handset"; break; case 0x11: case 0x1C: str = "server"; break; default: return; } DBG("chassis: %s", str); for (i = 0; chassis_table[i].chassis; i++) { if (!strcmp(chassis_table[i].chassis, str)) { major_class = chassis_table[i].major_class; minor_class = chassis_table[i].minor_class; break; } } DBG("major: 0x%02x minor: 0x%02x", major_class, minor_class); } static GDBusClient *hostname_client = NULL; static GDBusProxy *hostname_proxy = NULL; static int hostname_init(void) { DBusConnection *conn = btd_get_dbus_connection(); int fd; int err; read_dmi_fallback(); read_transient_hostname(); hostname_client = g_dbus_client_new(conn, "org.freedesktop.hostname1", "/org/freedesktop/hostname1"); if (!hostname_client) return -EIO; hostname_proxy = g_dbus_proxy_new(hostname_client, "/org/freedesktop/hostname1", "org.freedesktop.hostname1"); if (!hostname_proxy) { g_dbus_client_unref(hostname_client); hostname_client = NULL; return -EIO; } g_dbus_proxy_set_property_watch(hostname_proxy, property_changed, NULL); err = btd_register_adapter_driver(&hostname_driver); if (err < 0) { g_dbus_proxy_unref(hostname_proxy); hostname_proxy = NULL; g_dbus_client_unref(hostname_client); hostname_client = NULL; } fd = open("/proc/sys/kernel/hostname", O_RDONLY); if (fd < 0) { error("open(/proc/sys/kernel/hostname): %s (%d)", strerror(errno), errno); } else { GIOChannel *io = g_io_channel_unix_new(fd); hostname_id = g_io_add_watch(io, G_IO_ERR, hostname_cb, NULL); g_io_channel_unref(io); } return err; } static void hostname_exit(void) { btd_unregister_adapter_driver(&hostname_driver); if (hostname_proxy) { g_dbus_proxy_unref(hostname_proxy); hostname_proxy = NULL; } if (hostname_client) { g_dbus_client_unref(hostname_client); hostname_client = NULL; } if (hostname_id != 0) { g_source_remove(hostname_id); hostname_id = 0; } g_free(pretty_hostname); g_free(static_hostname); g_free(transient_hostname); } BLUETOOTH_PLUGIN_DEFINE(hostname, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, hostname_init, hostname_exit)