blob: c3ba17939d20b8c26f4658a1af64c12251b5b56b [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2017-2018 Codecoup
* Copyright (C) 2011-2014 Intel Corporation
* Copyright (C) 2002-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
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <time.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <libgen.h>
#include <errno.h>
#include <linux/capability.h>
#include "lib/bluetooth.h"
#include "lib/hci.h"
#include "src/shared/util.h"
#include "src/shared/mainloop.h"
#include "src/shared/btsnoop.h"
#include "src/systemd.h"
#define MONITOR_INDEX_NONE 0xffff
struct monitor_hdr {
uint16_t opcode;
uint16_t index;
uint16_t len;
} __attribute__ ((packed));
static struct btsnoop *btsnoop_file = NULL;
static void data_callback(int fd, uint32_t events, void *user_data)
{
uint8_t buf[BTSNOOP_MAX_PACKET_SIZE];
unsigned char control[64];
struct monitor_hdr hdr;
struct msghdr msg;
struct iovec iov[2];
if (events & (EPOLLERR | EPOLLHUP)) {
mainloop_exit_failure();
return;
}
iov[0].iov_base = &hdr;
iov[0].iov_len = sizeof(hdr);
iov[1].iov_base = buf;
iov[1].iov_len = sizeof(buf);
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iov;
msg.msg_iovlen = 2;
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
while (1) {
struct cmsghdr *cmsg;
struct timeval *tv = NULL;
struct timeval ctv;
uint16_t opcode, index, pktlen;
ssize_t len;
len = recvmsg(fd, &msg, MSG_DONTWAIT);
if (len < 0)
break;
if (len < (ssize_t) sizeof(hdr))
break;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level != SOL_SOCKET)
continue;
if (cmsg->cmsg_type == SCM_TIMESTAMP) {
memcpy(&ctv, CMSG_DATA(cmsg), sizeof(ctv));
tv = &ctv;
}
}
opcode = le16_to_cpu(hdr.opcode);
index = le16_to_cpu(hdr.index);
pktlen = le16_to_cpu(hdr.len);
btsnoop_write_hci(btsnoop_file, tv, index, opcode, 0, buf,
pktlen);
}
}
static bool open_monitor_channel(void)
{
struct sockaddr_hci addr;
int fd, opt = 1;
fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);
if (fd < 0) {
perror("Failed to open monitor channel");
return false;
}
memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = HCI_DEV_NONE;
addr.hci_channel = HCI_CHANNEL_MONITOR;
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
perror("Failed to bind monitor channel");
close(fd);
return false;
}
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt)) < 0) {
perror("Failed to enable timestamps");
close(fd);
return false;
}
if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &opt, sizeof(opt)) < 0) {
perror("Failed to enable credentials");
close(fd);
return false;
}
mainloop_add_fd(fd, EPOLLIN, data_callback, NULL, NULL);
return true;
}
static void signal_callback(int signum, void *user_data)
{
switch (signum) {
case SIGINT:
case SIGTERM:
mainloop_quit();
break;
}
}
extern int capget(struct __user_cap_header_struct *header,
struct __user_cap_data_struct *data);
extern int capset(struct __user_cap_header_struct *header,
const struct __user_cap_data_struct *data);
static void drop_capabilities(void)
{
struct __user_cap_header_struct header;
struct __user_cap_data_struct cap;
unsigned int mask;
int err;
header.version = _LINUX_CAPABILITY_VERSION_3;
header.pid = 0;
err = capget(&header, &cap);
if (err) {
perror("Unable to get current capabilities");
return;
}
/* not needed anymore since monitor socket is already open */
mask = ~CAP_TO_MASK(CAP_NET_RAW);
cap.effective &= mask;
cap.permitted &= mask;
cap.inheritable &= mask;
err = capset(&header, &cap);
if (err)
perror("Failed to set capabilities");
}
static void usage(void)
{
printf("btmon-logger - Bluetooth monitor\n"
"Usage:\n");
printf("\tbtmon-logger [options]\n");
printf("options:\n"
"\t-b, --basename <path> Save traces in specified path\n"
"\t-p, --parents Create basename parent directories\n"
"\t-l, --limit <limit> Limit traces file size (rotate)\n"
"\t-c, --count <count> Limit number of rotated files\n"
"\t-v, --version Show version\n"
"\t-h, --help Show help options\n");
}
static const struct option main_options[] = {
{ "basename", required_argument, NULL, 'b' },
{ "parents", no_argument, NULL, 'p' },
{ "limit", required_argument, NULL, 'l' },
{ "count", required_argument, NULL, 'c' },
{ "version", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{ }
};
static int create_dir(const char *filename)
{
char *dirc;
char *dir;
char *p;
int err = 0;
/* get base directory */
dirc = strdup(filename);
dir = dirname(dirc);
p = dir;
/* preserve leading / if present */
if (*p == '/')
p++;
/* create any intermediate directories */
p = strchrnul(p, '/');
while (*p) {
/* cut directory path */
*p = '\0';
if (mkdir(dir, 0700) < 0 && errno != EEXIST) {
err = errno;
goto done;
}
/* restore directory path */
*p = '/';
p = strchrnul(p + 1, '/');
}
/* create leaf directory */
if (mkdir(dir, 0700) < 0 && errno != EEXIST)
err = errno;
done:
free(dirc);
if (err)
printf("Failed to create parent directories for %s\n",
filename);
return err;
}
int main(int argc, char *argv[])
{
const char *path = "hci.log";
unsigned long max_count = 0;
size_t size_limit = 0;
bool parents = false;
int exit_status;
sigset_t mask;
char *endptr;
mainloop_init();
sd_notify(0, "STATUS=Starting up");
while (true) {
int opt;
opt = getopt_long(argc, argv, "b:l:c:vhp", main_options,
NULL);
if (opt < 0)
break;
switch (opt) {
case 'b':
path = optarg;
if (strlen(path) > PATH_MAX) {
fprintf(stderr, "Too long path\n");
return EXIT_FAILURE;
}
break;
case 'l':
size_limit = strtoul(optarg, &endptr, 10);
if (size_limit == ULONG_MAX) {
fprintf(stderr, "Invalid limit\n");
return EXIT_FAILURE;
}
if (*endptr != '\0') {
if (*endptr == 'K' || *endptr == 'k') {
size_limit *= 1024;
} else if (*endptr == 'M' || *endptr == 'm') {
size_limit *= 1024 * 1024;
} else {
fprintf(stderr, "Invalid limit\n");
return EXIT_FAILURE;
}
}
/* limit this to reasonable size */
if (size_limit < 4096) {
fprintf(stderr, "Too small limit value\n");
return EXIT_FAILURE;
}
break;
case 'c':
max_count = strtoul(optarg, &endptr, 10);
break;
case 'p':
if (getppid() != 1) {
fprintf(stderr, "Parents option allowed only "
"when running as a service\n");
return EXIT_FAILURE;
}
parents = true;
break;
case 'v':
printf("%s\n", VERSION);
return EXIT_SUCCESS;
case 'h':
usage();
return EXIT_SUCCESS;
default:
return EXIT_FAILURE;
}
}
if (argc - optind > 0) {
fprintf(stderr, "Invalid command line parameters\n");
return EXIT_FAILURE;
}
if (!open_monitor_channel())
return EXIT_FAILURE;
if (parents && create_dir(path) < 0)
return EXIT_FAILURE;
btsnoop_file = btsnoop_create(path, size_limit, max_count,
BTSNOOP_FORMAT_MONITOR);
if (!btsnoop_file)
return EXIT_FAILURE;
drop_capabilities();
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
mainloop_set_signal(&mask, signal_callback, NULL, NULL);
printf("Bluetooth monitor logger ver %s\n", VERSION);
sd_notify(0, "STATUS=Running");
sd_notify(0, "READY=1");
exit_status = mainloop_run();
sd_notify(0, "STATUS=Quitting");
btsnoop_unref(btsnoop_file);
return exit_status;
}