blob: 179152fdd298536a3a755597ee93018a7a0bfa6b [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2011 Red Hat, Inc.
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author: Bastien Nocera <hadess@hadess.net>
* Marcel Holtmann <marcel@holtmann.org> (for expand_name)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <glib.h>
#include <bluetooth/bluetooth.h>
#include "plugin.h"
#include "hcid.h" /* For main_opts */
#include "adapter.h"
#include "manager.h"
#include "device.h" /* Needed for storage.h */
#include "storage.h"
#include "log.h"
#include <sys/inotify.h>
#define MACHINE_INFO_DIR "/etc/"
#define MACHINE_INFO_FILE "machine-info"
static guint watchid = 0;
/* This file is part of systemd's hostnamed functionality:
* http://0pointer.de/public/systemd-man/machine-info.html
* http://www.freedesktop.org/wiki/Software/systemd/hostnamed
*/
static char *read_pretty_host_name(void)
{
char *contents, *ret;
char **lines;
guint i;
if (g_file_get_contents(MACHINE_INFO_DIR MACHINE_INFO_FILE,
&contents, NULL, NULL) == FALSE)
return NULL;
lines = g_strsplit_set(contents, "\r\n", 0);
g_free(contents);
if (lines == NULL)
return NULL;
ret = NULL;
for (i = 0; lines[i] != NULL; i++) {
if (g_str_has_prefix(lines[i], "PRETTY_HOSTNAME=")) {
ret = g_strdup(lines[i] + strlen("PRETTY_HOSTNAME="));
break;
}
}
g_strfreev(lines);
return ret;
}
/*
* Device name expansion
* %d - device id
* %h - hostname
*/
static char *expand_name(char *dst, int size, char *str, int dev_id)
{
register int sp, np, olen;
char *opt, buf[10];
if (!str || !dst)
return NULL;
sp = np = 0;
while (np < size - 1 && str[sp]) {
switch (str[sp]) {
case '%':
opt = NULL;
switch (str[sp+1]) {
case 'd':
sprintf(buf, "%d", dev_id);
opt = buf;
break;
case 'h':
opt = main_opts.host_name;
break;
case '%':
dst[np++] = str[sp++];
/* fall through */
default:
sp++;
continue;
}
if (opt) {
/* substitute */
olen = strlen(opt);
if (np + olen < size - 1)
memcpy(dst + np, opt, olen);
np += olen;
}
sp += 2;
continue;
case '\\':
sp++;
/* fall through */
default:
dst[np++] = str[sp++];
break;
}
}
dst[np] = '\0';
return dst;
}
static int get_default_adapter_id(void)
{
struct btd_adapter *default_adapter;
default_adapter = manager_get_default_adapter();
if (default_adapter == NULL)
return -1;
return adapter_get_dev_id(default_adapter);
}
static void set_pretty_name(struct btd_adapter *adapter,
const char *pretty_hostname)
{
int current_id;
int default_adapter;
default_adapter = get_default_adapter_id();
current_id = adapter_get_dev_id(adapter);
/* Allow us to change the name */
adapter_set_allow_name_changes(adapter, TRUE);
/* If it's the first device, let's assume it will be the
* default one, as we're not told when the default adapter
* changes */
if (default_adapter < 0)
default_adapter = current_id;
if (default_adapter != current_id) {
char *str;
/* +1 because we don't want an adapter called "Foobar's
* laptop #0" */
str = g_strdup_printf("%s #%d", pretty_hostname,
current_id + 1);
DBG("Setting name '%s' for device 'hci%d'", str, current_id);
adapter_set_name(adapter, str);
g_free(str);
} else {
DBG("Setting name '%s' for device 'hci%d'", pretty_hostname,
current_id);
adapter_set_name(adapter, pretty_hostname);
}
/* And disable the name change now */
adapter_set_allow_name_changes(adapter, FALSE);
}
static int adaptername_probe(struct btd_adapter *adapter)
{
int current_id;
char name[MAX_NAME_LENGTH + 1];
char *pretty_hostname;
pretty_hostname = read_pretty_host_name();
if (pretty_hostname != NULL) {
set_pretty_name(adapter, pretty_hostname);
g_free(pretty_hostname);
return 0;
}
adapter_set_allow_name_changes(adapter, TRUE);
current_id = adapter_get_dev_id(adapter);
if (btd_adapter_get_name(adapter) != NULL)
return 0;
expand_name(name, MAX_NAME_LENGTH, main_opts.name, current_id);
DBG("Setting name '%s' for device 'hci%d'", name, current_id);
adapter_set_name(adapter, name);
return 0;
}
static gboolean handle_inotify_cb(GIOChannel *channel, GIOCondition cond,
gpointer data)
{
struct inotify_event event;
gsize len;
GIOStatus err;
char name[FILENAME_MAX + 1];
while ((err = g_io_channel_read_chars(channel, (gchar *) &event,
sizeof(event), &len, NULL)) != G_IO_STATUS_AGAIN) {
if (err != G_IO_STATUS_NORMAL || len != sizeof(event) ||
event.len > sizeof(name))
goto fail;
if (event.len == 0)
continue;
err = g_io_channel_read_chars(channel, name, event.len, &len,
NULL);
if (err != G_IO_STATUS_NORMAL || len != event.len)
goto fail;
if (strncmp(name, MACHINE_INFO_FILE, event.len) == 0) {
DBG(MACHINE_INFO_DIR MACHINE_INFO_FILE
" changed, updating adapters' names");
manager_foreach_adapter((adapter_cb) adaptername_probe,
NULL);
break;
}
}
return TRUE;
fail:
error("Error reading inotify event");
return FALSE;
}
static struct btd_adapter_driver adaptername_driver = {
.name = "adaptername",
.probe = adaptername_probe,
};
struct inotify_data {
int inot_fd;
int watch_d;
};
static void destroy_cb(gpointer user_data)
{
struct inotify_data *data = user_data;
inotify_rm_watch(data->inot_fd, data->watch_d);
g_free(data);
}
static int adaptername_init(void)
{
guint32 mask;
GIOChannel *inotify;
int inot_fd;
int watch_d;
struct inotify_data *data;
inot_fd = inotify_init();
if (inot_fd < 0) {
int err = -errno;
error("Failed to setup inotify: %s (%d)", strerror(-err), -err);
return err;
}
mask = IN_CLOSE_WRITE;
mask |= IN_DELETE;
mask |= IN_CREATE;
mask |= IN_MOVED_FROM;
mask |= IN_MOVED_TO;
watch_d = inotify_add_watch(inot_fd, MACHINE_INFO_DIR, mask);
if (watch_d < 0) {
int err = -errno;
error("Failed to setup watch for '%s': %s (%d)",
MACHINE_INFO_DIR, strerror(-err), -err);
close(inot_fd);
return err;
}
data = g_new(struct inotify_data, 1);
data->inot_fd = inot_fd;
data->watch_d = watch_d;
inotify = g_io_channel_unix_new(inot_fd);
g_io_channel_set_close_on_unref(inotify, TRUE);
g_io_channel_set_encoding(inotify, NULL, NULL);
g_io_channel_set_flags(inotify, G_IO_FLAG_NONBLOCK, NULL);
watchid = g_io_add_watch_full(inotify, G_PRIORITY_DEFAULT, G_IO_IN,
handle_inotify_cb, data, destroy_cb);
g_io_channel_unref(inotify);
btd_register_adapter_driver(&adaptername_driver);
return 0;
}
static void adaptername_exit(void)
{
if (watchid > 0) {
g_source_remove(watchid);
watchid = 0;
}
btd_unregister_adapter_driver(&adaptername_driver);
}
BLUETOOTH_PLUGIN_DEFINE(adaptername, VERSION,
BLUETOOTH_PLUGIN_PRIORITY_LOW, adaptername_init, adaptername_exit)