blob: d9b701816ebbd3d020d8f102feec3c1436bdf396 [file] [log] [blame]
/*
* Copyright (c) 2011-2012 - Mauro Carvalho Chehab
*
* 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 version 2
* of the License.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
* Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
*/
#define _XOPEN_SOURCE 600
#define _FILE_OFFSET_BITS 64
#define _LARGEFILE_SOURCE 1
#define _LARGEFILE64_SOURCE 1
#include <config.h>
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
#endif
#include <argp.h>
#include <config.h>
#include <endian.h>
#include <netinet/in.h>
#include <poll.h>
#include <pthread.h>
#include <search.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <syslog.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include "../../lib/libdvbv5/dvb-fe-priv.h"
#include "../../lib/libdvbv5/dvb-dev-priv.h"
#include "libdvbv5/dvb-file.h"
#include "libdvbv5/dvb-dev.h"
#ifdef ENABLE_NLS
# define _(string) gettext(string)
# include "gettext.h"
# include <locale.h>
# include <langinfo.h>
# include <iconv.h>
#else
# define _(string) string
#endif
# define N_(string) string
/* Max number of open files */
#define NUM_FOPEN 1024
/*
* Argument processing data and logic
*/
#define PROGRAM_NAME "dvbv5-daemon"
const char *argp_program_version = PROGRAM_NAME " version " V4L_UTILS_VERSION;
const char *argp_program_bug_address = "Mauro Carvalho Chehab <mchehab@kernel.org>";
#define CMD_SIZE 80
static const char doc[] = N_(
"\nA DVB remote daemon using API version 5\n");
static const struct argp_option options[] = {
{"verbose", 'v', 0, 0, N_("enables debug messages"), 0},
{"port", 'p', "5555", 0, N_("port to listen"), 0},
{"help", '?', 0, 0, N_("Give this help list"), -1},
{"usage", -3, 0, 0, N_("Give a short usage message")},
{"version", 'V', 0, 0, N_("Print program version"), -1},
{ 0, 0, 0, 0, 0, 0 }
};
static int port = 0;
static int verbose = 0;
static error_t parse_opt(int k, char *arg, struct argp_state *state)
{
switch (k) {
case 'p':
port = atoi(arg);
break;
case 'v':
verbose ++;
break;
case '?':
argp_state_help(state, state->out_stream,
ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG
| ARGP_HELP_DOC);
fprintf(state->out_stream, _("\nReport bugs to %s.\n"), argp_program_bug_address);
exit(0);
case 'V':
fprintf (state->out_stream, "%s\n", argp_program_version);
exit(0);
case -3:
argp_state_help(state, state->out_stream, ARGP_HELP_USAGE);
exit(0);
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = {
.options = options,
.parser = parse_opt,
.doc = doc,
};
/*
* Local log
*
* Error messages should ideally be sent to the remote end, but, if
* an error happens during the processing of a packet, we need to
* report locally, as reporting remotely may not work.
*/
static const struct loglevel {
const char *name;
const char *color;
int fd;
} loglevels[9] = {
{N_("EMERG "), "\033[31m", STDERR_FILENO },
{N_("ALERT "), "\033[31m", STDERR_FILENO },
{N_("CRITICAL "), "\033[31m", STDERR_FILENO },
{N_("ERROR "), "\033[31m", STDERR_FILENO },
{N_("WARNING "), "\033[33m", STDOUT_FILENO },
{NULL, "\033[36m", STDOUT_FILENO }, /* NOTICE */
{NULL, NULL, STDOUT_FILENO }, /* INFO */
{N_("DEBUG "), "\033[32m", STDOUT_FILENO },
{NULL, "\033[0m", STDOUT_FILENO }, /* reset*/
};
#define LOG_COLOROFF 8
void local_log(int level, const char *fmt, ...)
{
if(level > sizeof(loglevels) / sizeof(struct loglevel) - 2) // ignore LOG_COLOROFF as well
level = LOG_INFO;
va_list ap;
va_start(ap, fmt);
FILE *out = stdout;
if (STDERR_FILENO == loglevels[level].fd)
out = stderr;
if (loglevels[level].color && isatty(loglevels[level].fd))
fputs(loglevels[level].color, out);
if (loglevels[level].name)
fprintf(out, "%s", _(loglevels[level].name));
vfprintf(out, fmt, ap);
fprintf(out, "\n");
if(loglevels[level].color && isatty(loglevels[level].fd))
fputs(loglevels[LOG_COLOROFF].color, out);
va_end(ap);
}
#define info(fmt, arg...) do {\
local_log(LOG_INFO, fmt, ##arg); \
} while (0)
#define warn(fmt, arg...) do {\
local_log(LOG_WARNING, fmt, ##arg); \
} while (0)
#define err(fmt, arg...) do {\
local_log(LOG_ERR, "%s: " fmt, __FUNCTION__, ##arg); \
} while (0)
#define dbg(fmt, arg...) do {\
local_log(LOG_DEBUG, "%s: " fmt, __FUNCTION__, ##arg); \
} while (0)
#define local_perror(msg) do {\
local_log(LOG_ERR, "%s: %s: %m (%d)", __FUNCTION__, msg, errno); \
} while (0)
/*
* Static data used by the code
*/
static pthread_mutex_t msg_mutex;
static pthread_mutex_t dvb_read_mutex;
static pthread_t read_id = 0;
struct dvb_descriptors {
int uid;
struct dvb_open_descriptor *open_dev;
};
static struct dvb_device *dvb = NULL;
static void *desc_root = NULL;
static int dvb_fd = -1;
static struct pollfd fds[NUM_FOPEN];
static nfds_t numfds = 0;
static char output_charset[256] = "utf-8";
static char default_charset[256] = "iso-8859-1";
static void stack_dump(void)
{
#ifdef HAVE_BACKTRACE
int i, nptrs = 0;
void *buffer[10];
char **strings = NULL;
nptrs = backtrace(buffer, sizeof(buffer));
if (nptrs) {
strings = backtrace_symbols(buffer, nptrs);
dbg("Stack:");
}
for (i = 0; i < nptrs; i++)
dbg(" %s", strings[i]);
free(strings);
#endif
}
/*
* Open dev descriptor handling
*/
static int dvb_desc_compare(const void *__a, const void *__b)
{
const struct dvb_descriptors *a = __a, *b = __b;
return (b->uid - a->uid);
}
static struct dvb_open_descriptor *get_open_dev(int uid)
{
struct dvb_descriptors desc, **p;
if (!desc_root)
return NULL;
desc.uid = uid;
p = tfind(&desc, &desc_root, dvb_desc_compare);
if (!p) {
err("open element not retrieved!");
return NULL;
}
return (*p)->open_dev;
}
static void destroy_open_dev(int uid)
{
struct dvb_descriptors desc, **p;
desc.uid = uid;
p = tdelete(&desc, &desc_root, dvb_desc_compare);
if (!p)
err("can't destroy opened element");
}
static void free_opendevs(void *node)
{
struct dvb_descriptors *desc = node;
if (verbose)
dbg("closing dev %p", desc, desc->open_dev);
dvb_dev_close(desc->open_dev);
free (desc);
}
static void close_all_devs(void)
{
dvb_fd = -1;
numfds = 0;
tdestroy(desc_root, free_opendevs);
desc_root = NULL;
}
/*
* Signal handling logic
*/
static void sigterm_handler(int const sig_class)
{
info(PROGRAM_NAME" interrupted.");
pthread_exit(NULL);
close_all_devs();
dvb_dev_free(dvb);
}
static void start_signal_handler(void)
{
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = sigterm_handler;
sigaction(SIGTERM, &action, NULL);
}
static void stop_signal_handler(void)
{
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = SIG_DFL;
sigaction(SIGTERM, &action, NULL);
}
/*
* Functions to send/receive messages to the client
*/
static ssize_t __prepare_data(char *buf, const size_t size,
const char *fmt, va_list ap)
{
char *p = buf, *endp = &buf[size], *s;
int len;
int32_t i32;
uint64_t u64;
while (*fmt && *fmt != '%') fmt++;
if (*fmt == '%') fmt++;
while (*fmt) {
switch (*fmt++) {
case 's': /* string */
s = va_arg(ap, char *);
if (s)
len = strlen(s);
else
len = 0;
if (p + len + 4 > endp) {
dbg("buffer to short for string");
stack_dump();
return -1;
}
i32 = htobe32(len);
memcpy(p, &i32, 4);
p += 4;
if (s)
memcpy(p, s, len);
p += len;
break;
case 'p': /* binary data with specified length */
s = va_arg(ap, char *);
len = va_arg(ap, ssize_t);
if (p + len + 4 > endp) {
dbg("buffer to short for string");
stack_dump();
return -1;
}
i32 = htobe32(len);
memcpy(p, &i32, 4);
p += 4;
memcpy(p, s, len);
p += len;
break;
case 'i': /* 32-bit int */
if (p + 4 > endp) {
dbg("buffer to short for int32_t");
stack_dump();
return -1;
}
i32 = htobe32(va_arg(ap, int32_t));
memcpy(p, &i32, 4);
p += 4;
break;
case 'l': /* 64-bit unsigned int */
if (*fmt++ != 'u') {
dbg("invalid long format character: '%c'", *fmt);
stack_dump();
break;
}
if (p + 8 > endp) {
dbg("buffer to short for uint64_t");
stack_dump();
return -1;
}
u64 = htobe64(va_arg(ap, uint64_t));
memcpy(p, &u64, 8);
p += 8;
break;
default:
dbg("invalid format character: '%c'", *fmt);
stack_dump();
}
while (*fmt && *fmt != '%') fmt++;
if (*fmt == '%') fmt++;
}
return p - buf;
}
static ssize_t prepare_data(char *buf, const size_t size,
const char *fmt, ...)
__attribute__ (( format( printf, 3, 4 )));
static ssize_t prepare_data(char *buf, const size_t size,
const char *fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = __prepare_data(buf, size, fmt, ap);
va_end(ap);
return ret;
}
static int send_buf(int fd, const char *buf, size_t size)
{
int ret;
int32_t i32;
if (fd < 0)
return ECONNRESET;
pthread_mutex_lock(&msg_mutex);
i32 = htobe32(size);
ret = send(fd, (void *)&i32, 4, MSG_MORE);
if (ret >= 0)
ret = send(fd, buf, size, 0);
pthread_mutex_unlock(&msg_mutex);
if (ret < 0) {
local_perror("write");
if (ret == ECONNRESET)
close_all_devs();
return errno;
}
return ret;
}
static ssize_t send_data(int fd, const char *fmt, ...)
__attribute__ (( format( printf, 2, 3 )));
static ssize_t send_data(int fd, const char *fmt, ...)
{
char buf[REMOTE_BUF_SIZE];
va_list ap;
int ret;
if (verbose)
dbg("called %s(fd, \"%s\", ...)", __FUNCTION__, fmt);
va_start(ap, fmt);
ret = __prepare_data(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (ret < 0)
return ret;
return send_buf(fd, buf, ret);
}
static ssize_t scan_data(char *buf, int buf_size, const char *fmt, ...)
__attribute__ (( format( scanf, 3, 4 )));
static ssize_t scan_data(char *buf, int buf_size, const char *fmt, ...)
{
char *p = buf, *endp = &buf[buf_size], *s;
int len;
int32_t *i32;
uint64_t *u64;
va_list ap;
va_start(ap, fmt);
while (*fmt && *fmt != '%') fmt++;
if (*fmt == '%') fmt++;
while (*fmt) {
switch (*fmt++) {
case 's': /* string */
s = va_arg(ap, char *);
if (p + 4 > endp) {
dbg("buffer to short for string length");
stack_dump();
return -1;
}
len = be32toh(*(int32_t *)p);
p += 4;
if (p + len > endp) {
dbg("buffer to short for string");
stack_dump();
return -1;
}
memcpy(s, p, len);
s[len] = '\0';
p += len;
break;
case 'i': /* 32-bit int */
if (p + 4 > endp) {
dbg("buffer to short for int32_t");
stack_dump();
return -1;
}
i32 = va_arg(ap, int32_t *);
*i32 = be32toh(*(int32_t *)p);
p += 4;
break;
case 'l': /* 64-bit unsigned int */
if (*fmt++ != 'u') {
dbg("invalid long format character: '%c'", *fmt);
stack_dump();
break;
}
if (p + 8 > endp) {
dbg("buffer to short for uint64_t");
stack_dump();
return -1;
}
u64 = va_arg(ap, uint64_t *);
*u64 = be32toh(*(uint64_t *)p);
p += 8;
break;
default:
dbg("invalid format character: '%c'", *fmt);
stack_dump();
}
while (*fmt && *fmt != '%') fmt++;
if (*fmt == '%') fmt++;
}
va_end(ap);
return p - buf;
}
/*
* Remote log
*/
void dvb_remote_log(void *priv, int level, const char *fmt, ...)
{
int ret;
char *buf;
va_list ap;
int fd = *(int*)priv;
va_start(ap, fmt);
ret = vasprintf(&buf, fmt, ap);
if (ret <= 0) {
local_perror("vasprintf");
return;
}
va_end(ap);
if (fd >= 0)
send_data(fd, "%i%s%i%s", 0, "log", level, buf);
else
local_log(level, buf);
free(buf);
}
static int dev_change_monitor(char *sysname,
enum dvb_dev_change_type type, void *user_priv)
{
send_data(dvb_fd, "%i%s%i%s", 0, "dev_change", type, sysname);
return 0;
}
/*
* command handler methods
*/
static int daemon_get_version(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
int ret = 0;
return send_data(fd, "%i%s%i%s", seq, cmd, ret, argp_program_version);
}
static int dev_find(uint32_t seq, char *cmd, int fd, char *buf, ssize_t size)
{
int enable_monitor = 0, ret;
dvb_dev_change_t handler = NULL;
ret = scan_data(buf, size, "%i", &enable_monitor);
if (ret < 0)
goto error;
if (enable_monitor)
handler = &dev_change_monitor;
ret = dvb_dev_find(dvb, handler, NULL);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_stop_monitor(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
dvb_dev_stop_monitor(dvb);
return send_data(fd, "%i%s%i", seq, cmd, 0);
}
static int dev_seek_by_adapter(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
struct dvb_dev_list *dev;
int adapter, num, type, ret;
ret = scan_data(buf, size, "%i%i%i", &adapter, &num, &type);
if (ret < 0)
goto error;
dev = dvb_dev_seek_by_adapter(dvb, adapter, num, type);
if (!dev)
goto error;
return send_data(fd, "%i%s%i%s%s%s%i%s%s%s%s%s", seq, cmd, ret,
dev->syspath, dev->path, dev->sysname, dev->dvb_type,
dev->bus_addr, dev->bus_id, dev->manufacturer,
dev->product, dev->serial);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_get_dev_info(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
struct dvb_dev_list *dev;
char sysname[REMOTE_BUF_SIZE];
int ret;
ret = scan_data(buf, size, "%s", sysname);
if (ret < 0)
goto error;
dev = dvb_get_dev_info(dvb, sysname);
if (!dev)
goto error;
return send_data(fd, "%i%s%i%s%s%s%i%s%s%s%s%s", seq, cmd, ret,
dev->syspath, dev->path, dev->sysname, dev->dvb_type,
dev->bus_addr, dev->bus_id, dev->manufacturer,
dev->product, dev->serial);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static void *read_data(void *privdata)
{
struct dvb_open_descriptor *open_dev;
int timeout;
int ret, read_ret = -1, fd, i;
char databuf[REMOTE_BUF_SIZE];
char buf[REMOTE_BUF_SIZE + 32], *p;
size_t size;
size_t count;
struct pollfd __fds[NUM_FOPEN];
nfds_t __numfds;
timeout = 10; /* ms */
while (1) {
pthread_mutex_lock(&dvb_read_mutex);
if (!numfds) {
pthread_mutex_unlock(&dvb_read_mutex);
break;
}
__numfds = numfds;
memcpy(__fds, fds, sizeof(fds));
pthread_mutex_unlock(&dvb_read_mutex);
ret = poll(__fds, __numfds, timeout);
if (!ret)
continue;
if (ret < 0) {
err("poll");
continue;
}
for (i = 0; i < __numfds; i++) {
if (__fds[i].revents & (POLLERR | POLLHUP | POLLNVAL))
continue;
if (__fds[i].revents)
break;
}
/*
* it means that one error condition happened.
* Likely the file was closed.
*/
if (i == __numfds)
continue;
fd = __fds[i].fd;
if (!desc_root)
break;
open_dev = get_open_dev(fd);
if (!open_dev) {
err("Couldn't find opened file %d", fd);
continue;
}
count = REMOTE_BUF_SIZE;
read_ret = dvb_dev_read(open_dev, databuf, count);
if (verbose) {
if (read_ret < 0)
dbg("#%d: read error: %d on %p", fd, read_ret, open_dev);
else
dbg("#%d: read %ul bytes (count %d)", fd, read_ret, count);
}
/* Initialize to the start of the buffer */
p = buf;
size = sizeof(buf);
ret = prepare_data(p, size, "%i%s%i%i", 0, "data_read",
read_ret, fd);
if (ret < 0) {
err("Failed to prepare answer to dvb_read()");
break;
}
p += ret;
size -= ret;
if (read_ret > 0) {
if (read_ret > size) {
dbg("buffer to short to store read data!");
read_ret = -EOVERFLOW;
} else {
memcpy(p, databuf, read_ret);
p += read_ret;
}
}
ret = send_buf(dvb_fd, buf, p - buf);
if (ret < 0) {
err("Error %d sending buffer\n", ret);
if (ret == ECONNRESET) {
close_all_devs();
break;
}
continue;
}
}
dbg("Finishing kthread");
read_id = 0;
return NULL;
}
static int dev_open(uint32_t seq, char *cmd, int fd, char *buf, ssize_t size)
{
struct dvb_open_descriptor *open_dev;
struct dvb_dev_list *dev;
struct dvb_descriptors *desc, **p;
int ret, flags, uid;
char sysname[REMOTE_BUF_SIZE];
desc = calloc(1, sizeof(*desc));
if (!desc) {
local_perror("calloc");
ret = -ENOMEM;
goto error;
}
ret = scan_data(buf, size, "%s%i", sysname, &flags);
if (ret < 0) {
free(desc);
goto error;
}
/*
* Discard requests for O_NONBLOCK, as the daemon will use threads
* to handle unblocked reads.
*/
flags &= ~O_NONBLOCK;
open_dev = dvb_dev_open(dvb, sysname, flags);
if (!open_dev) {
ret = -errno;
free(desc);
goto error;
}
if (verbose)
dbg("open dev handler for %s: %p with uid#%d", sysname, open_dev, open_dev->fd);
dev = open_dev->dev;
if (dev->dvb_type == DVB_DEVICE_DEMUX ||
dev->dvb_type == DVB_DEVICE_DVR) {
pthread_mutex_lock(&dvb_read_mutex);
fds[numfds].fd = open_dev->fd;
fds[numfds].events = POLLIN | POLLPRI;
numfds++;
pthread_mutex_unlock(&dvb_read_mutex);
}
if (!read_id) {
ret = pthread_create(&read_id, NULL, read_data, NULL);
if (ret < 0) {
local_perror("pthread_create");
pthread_mutex_unlock(&msg_mutex);
free(desc);
return -1;
}
}
pthread_mutex_unlock(&msg_mutex);
uid = open_dev->fd;
desc->uid = uid;
desc->open_dev = open_dev;
/* Add element to the desc_root tree */
p = tsearch(desc, &desc_root, dvb_desc_compare);
if (!p) {
local_perror("tsearch");
uid = 0;
} else if (*p != desc) {
err("uid %d was already opened!", uid);
}
pthread_mutex_unlock(&msg_mutex);
ret = uid;
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_close(uint32_t seq, char *cmd, int fd, char *buf, ssize_t size)
{
struct dvb_open_descriptor *open_dev;
int uid, ret, i;
ret = scan_data(buf, size, "%i", &uid);
if (ret < 0)
goto error;
open_dev = get_open_dev(uid);
if (!open_dev) {
err("Can't find uid to close");
ret = -1;
goto error;
}
/* Delete fd from the opened array */
pthread_mutex_lock(&dvb_read_mutex);
for (i = 0; i < numfds; i++) {
if (fds[i].fd != open_dev->fd)
continue;
if (i < numfds - 1)
memmove(&fds[i], &fds[i + 1],
sizeof(*fds)*(numfds - i - 1));
numfds--;
break;
}
pthread_mutex_unlock(&dvb_read_mutex);
if (read_id && !numfds) {
pthread_cancel(read_id);
read_id = 0;
}
dvb_dev_close(open_dev);
destroy_open_dev(uid);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_dmx_stop(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
struct dvb_open_descriptor *open_dev;
int uid, ret;
ret = scan_data(buf, size, "%i", &uid);
if (ret < 0)
goto error;
open_dev = get_open_dev(uid);
if (!open_dev) {
ret = -1;
err("Can't find uid to stop");
goto error;
}
dvb_dev_dmx_stop(open_dev);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_set_bufsize(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
struct dvb_open_descriptor *open_dev;
int uid, ret, bufsize;
ret = scan_data(buf, size, "%i%i", &uid, &bufsize);
if (ret < 0)
goto error;
open_dev = get_open_dev(uid);
if (!open_dev) {
ret = -1;
err("Can't find uid to stop");
goto error;
}
dvb_dev_set_bufsize(open_dev, bufsize);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_dmx_set_pesfilter(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
struct dvb_open_descriptor *open_dev;
int uid, ret, pid, type, output, bufsize;
ret = scan_data(buf, size, "%i%i%i%i%i",
&uid, &pid, &type, &output, &bufsize);
if (ret < 0)
goto error;
open_dev = get_open_dev(uid);
if (!open_dev) {
ret = -1;
err("Can't find uid to set pesfilter");
goto error;
}
ret = dvb_dev_dmx_set_pesfilter(open_dev, pid, type, output, bufsize);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_dmx_set_section_filter(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
struct dvb_open_descriptor *open_dev;
int uid, ret, pid, filtsize, flags;
unsigned char filter[17], mask[17], mode[17];
ret = scan_data(buf, size, "%i%i%i%s%s%s%i",
&uid, &pid, &filtsize, filter, mask, mode, &flags);
if (ret < 0)
goto error;
open_dev = get_open_dev(uid);
if (!open_dev) {
ret = -1;
err("Can't find uid to set section filter");
goto error;
}
ret = dvb_dev_dmx_set_section_filter(open_dev, pid, filtsize, filter,
mask, mode, flags);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_dmx_get_pmt_pid(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
struct dvb_open_descriptor *open_dev;
int uid, ret, sid;
ret = scan_data(buf, size, "%i%i", &uid, &sid);
if (ret < 0)
goto error;
open_dev = get_open_dev(uid);
if (!open_dev) {
ret = -1;
err("Can't find uid to get PMT PID");
goto error;
}
ret = dvb_dev_dmx_get_pmt_pid(open_dev, sid);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_scan(uint32_t seq, char *cmd, int fd, char *buf, ssize_t size)
{
int ret = -1;
/*
* FIXME: There are too many stuff here! Maybe we should
* split this into smaller per-table calls.
*/
#if 0
struct dvb_open_descriptor *open_dev;
int uid;
ret = scan_data(buf, size, "%i%i", &uid);
if (ret < 0)
goto error;
open_dev = get_open_dev(uid);
if (!open_dev) {
ret = -1;
err("Can't find uid to scan");
goto error;
}
ret = dvb_scan(foo);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
#else
return send_data(fd, "%i%s%i", seq, cmd, ret);
#endif
}
static int dev_set_sys(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
struct dvb_v5_fe_parms_priv *parms = (void *)dvb->fe_parms;
struct dvb_v5_fe_parms *p = (void *)parms;
int sys = 0, ret;
ret = scan_data(buf, size, "%i", &sys);
if (ret < 0)
goto error;
ret = __dvb_set_sys(p, sys);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_get_parms(uint32_t seq, char *cmd, int fd,
char *inbuf, ssize_t insize)
{
struct dvb_v5_fe_parms_priv *parms = (void *)dvb->fe_parms;
struct dvb_v5_fe_parms *par = (void *)parms;
struct dvb_frontend_info *info = &par->info;
int ret, i;
char buf[REMOTE_BUF_SIZE], lnb_name[80] = "", *p = buf;
size_t size = sizeof(buf);
if (verbose)
dbg("dev_get_parms called");
ret = __dvb_fe_get_parms(par);
if (ret < 0)
goto error;
/* Send first the public params */
ret = prepare_data(p, size, "%i%s%i%s%i%i%i%i%i%i%i", seq, cmd, ret,
info->name, info->frequency_min,
info->frequency_max, info->frequency_stepsize,
info->frequency_tolerance, info->symbol_rate_min,
info->symbol_rate_max, info->symbol_rate_tolerance);
if (ret < 0)
goto error;
p += ret;
size -= ret;
if (par->lnb)
strcpy(lnb_name, par->lnb->name);
ret = prepare_data(p, size, "%i%i%i%i%i%i%i%s%i%i%i%i%s%s",
par->version, par->has_v5_stats, par->current_sys,
par->num_systems, par->legacy_fe, par->abort,
par->lna, lnb_name,
par->sat_number, par->freq_bpf, par->diseqc_wait,
par->verbose, par->default_charset,
par->output_charset);
if (ret < 0)
goto error;
p += ret;
size -= ret;
for (i = 0; i < MAX_DELIVERY_SYSTEMS; i++) {
ret = prepare_data(p, size, "%i", par->systems[i]);
if (ret < 0)
goto error;
p += ret;
size -= ret;
}
/* Now, send the private ones - except for stats */
ret = prepare_data(p, size, "%i%i%i%i",
parms->n_props,
parms->country,
parms->high_band,
parms->freq_offset);
if (ret < 0)
goto error;
p += ret;
size -= ret;
for (i = 0; i < parms->n_props; i++) {
ret = prepare_data(p, size, "%i%i",
parms->dvb_prop[i].cmd,
parms->dvb_prop[i].u.data);
if (ret < 0)
goto error;
p += ret;
size -= ret;
}
strcpy(output_charset, par->output_charset);
strcpy(default_charset, par->default_charset);
return send_buf(fd, buf, p - buf);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_set_parms(uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size)
{
struct dvb_v5_fe_parms_priv *parms = (void *)dvb->fe_parms;
struct dvb_v5_fe_parms *par = (void *)parms;
int ret, i;
char *p = buf;
const char *old_lnb = "";
char new_lnb[256];
if (verbose)
dbg("dev_set_parms called");
/* first the public params that aren't read only */
/* Get current LNB name */
if (par->lnb)
old_lnb = par->lnb->name;
ret = scan_data(p, size, "%i%i%s%i%i%i%i%s%s",
&par->abort, &par->lna, new_lnb,
&par->sat_number, &par->freq_bpf, &par->diseqc_wait,
&par->verbose, default_charset, output_charset);
if (ret < 0)
goto error;
p += ret;
size -= ret;
/* Now, the private ones */
ret = scan_data(p, size, "%i", &i);
if (ret < 0)
goto error;
parms->country = i;
p += ret;
size -= ret;
for (i = 0; i < parms->n_props; i++) {
ret = scan_data(p, size, "%i%i",
&parms->dvb_prop[i].cmd,
&parms->dvb_prop[i].u.data);
if (ret < 0)
goto error;
p += ret;
size -= ret;
}
if (!*new_lnb) {
par->lnb = NULL;
} else if (strcmp(old_lnb, new_lnb)) {
int lnb = dvb_sat_search_lnb(new_lnb);
if (lnb < 0) {
dvb_logerr("Invalid lnb: %s", new_lnb);
ret = -1;
goto error;
}
par->lnb = dvb_sat_get_lnb(lnb);
}
par->output_charset = output_charset;
par->default_charset = default_charset;
ret = __dvb_fe_set_parms(par);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
static int dev_get_stats(uint32_t seq, char *cmd, int fd,
char *inbuf, ssize_t insize)
{
struct dvb_v5_fe_parms_priv *parms = (void *)dvb->fe_parms;
struct dvb_v5_stats *st = &parms->stats;
struct dvb_v5_fe_parms *par = (void *)parms;
int ret, i;
char buf[REMOTE_BUF_SIZE], *p = buf;
size_t size = sizeof(buf);
if (verbose)
dbg("dev_get_stats called");
ret = __dvb_fe_get_stats(par);
if (ret < 0)
goto error;
ret = prepare_data(p, size, "%i%s%i%i", seq, cmd, ret, st->prev_status);
if (ret < 0)
goto error;
p += ret;
size -= ret;
for (i = 0; i < DTV_NUM_STATS_PROPS; i++) {
ret = prepare_data(p, size, "%i%i",
st->prop[i].cmd,
st->prop[i].u.data);
if (ret < 0)
goto error;
p += ret;
size -= ret;
}
for (i = 0; i < MAX_DTV_STATS; i++) {
struct dvb_v5_counters *prev = st->prev;
struct dvb_v5_counters *cur = st->cur;
ret = prepare_data(p, size, "%i%i%i",
st->has_post_ber[i],
st->has_pre_ber[i],
st->has_per[i]);
if (ret < 0)
goto error;
p += ret;
size -= ret;
#pragma GCC diagnostic ignored "-Wformat"
ret = prepare_data(p, size,
"%lu%lu%lu%lu%lu%lu%lu%lu%lu%lu%lu%lu",
prev->pre_bit_count,
prev->pre_bit_error,
prev->post_bit_count,
prev->post_bit_error,
prev->block_count,
prev->block_error,
cur->pre_bit_count,
cur->pre_bit_error,
cur->post_bit_count,
cur->post_bit_error,
cur->block_count,
cur->block_error);
#pragma GCC diagnostic pop
if (ret < 0)
goto error;
p += ret;
size -= ret;
}
return send_buf(fd, buf, p - buf);
error:
return send_data(fd, "%i%s%i", seq, cmd, ret);
}
/*
* Structure with all methods with RPC calls
*/
typedef int (*method_handler) (uint32_t seq, char *cmd, int fd,
char *buf, ssize_t size);
struct method_types {
char *name;
method_handler handler;
int locks_dvb;
};
static const struct method_types methods[] = {
{"daemon_get_version", &daemon_get_version, 1},
{"dev_find", &dev_find, 0},
{"dev_stop_monitor", &dev_stop_monitor, 0},
{"dev_seek_by_adapter", &dev_seek_by_adapter, 0},
{"dev_get_dev_info", &dev_get_dev_info, 0},
{"dev_open", &dev_open, 0},
{"dev_close", &dev_close, 0},
{"dev_dmx_stop", &dev_dmx_stop, 0},
{"dev_set_bufsize", &dev_set_bufsize, 0},
{"dev_dmx_set_pesfilter", &dev_dmx_set_pesfilter, 0},
{"dev_dmx_set_section_filter", &dev_dmx_set_section_filter, 0},
{"dev_dmx_get_pmt_pid", &dev_dmx_get_pmt_pid, 0},
{"dev_scan", &dev_scan, 0},
{"dev_set_sys", &dev_set_sys, 0},
{"fe_get_parms", &dev_get_parms, 0},
{"fe_set_parms", &dev_set_parms, 0},
{"fe_get_stats", &dev_get_stats, 0},
{}
};
static void *start_server(void *fd_pointer)
{
const struct method_types *method;
int fd = *(int *)fd_pointer, ret, flag = 1;
char buf[REMOTE_BUF_SIZE + 8], cmd[CMD_SIZE], *p;
ssize_t size;
uint32_t seq;
int bufsize;
if (verbose)
dbg("Opening socket %d", fd);
/* Set a large buffer for read() to work better */
bufsize = REMOTE_BUF_SIZE;
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF,
(void *)&bufsize, (int)sizeof(bufsize))) {
dbg("Failed to set a large buffer size");
};
/* Disable Naggle algorithm, as we want errors to be sent ASAP */
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int))) {
dbg("Failed to avoid TCP delays");
};
/* Command dispatcher */
do {
size = recv(fd, buf, 4, MSG_WAITALL);
if (size <= 0)
break;
size = (uint32_t)buf[0] << 24 | (uint32_t)buf[1] << 16 |
(uint32_t)buf[2] << 8 | (uint32_t)buf[3];
size = recv(fd, buf, size, MSG_WAITALL);
if (size <= 0)
break;
ret = scan_data(buf, size, "%i%s", &seq, cmd);
if (ret < 0) {
if (verbose)
dbg("message too short: %ld", size);
send_data(fd, "%i%s%i%s", 0, "log", LOG_ERR,
"msg too short");
continue;
}
p = buf + ret;
size -= ret;
if (verbose)
dbg("received command: %i '%s'", seq, cmd);
if (size > buf + sizeof(buf) - p) {
if (verbose)
dbg("data length too big: %d", size);
send_data(fd, "%i%s%i%s", 0, "log", LOG_ERR,
"data length too big");
continue;
}
method = methods;
while (method->name) {
if (!strcmp(cmd, method->name)) {
if (dvb_fd > 0 || method->locks_dvb) {
ret = method->handler(seq, cmd,
fd, p, size);
if (ret < 0)
break;
if (method->locks_dvb)
dvb_fd = fd;
break;
}
send_data(fd, "%i%s%i%s", 0, "log", LOG_ERR,
"daemon busy");
break;
}
method++;
}
if (!method->name) {
if (verbose)
dbg("invalid command: %s", cmd);
send_data(fd, "%i%s%i%s", 0, "log", LOG_ERR,
"invalid command");
}
} while (1);
if (verbose)
dbg("Closing socket %d", fd);
close(fd);
if (read_id) {
pthread_cancel(read_id);
read_id = 0;
}
if (dvb_fd > 0)
close_all_devs();
return NULL;
}
/*
* main program
*/
int main(int argc, char *argv[])
{
int ret;
int sockfd;
socklen_t addrlen;
struct sockaddr_in serv_addr, cli_addr;
#ifdef ENABLE_NLS
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
#endif
if (argp_parse(&argp, argc, argv, ARGP_NO_HELP | ARGP_NO_EXIT, 0, 0)) {
argp_help(&argp, stderr, ARGP_HELP_SHORT_USAGE, PROGRAM_NAME);
return -1;
}
if (!port) {
argp_help(&argp, stderr, ARGP_HELP_SHORT_USAGE, PROGRAM_NAME);
return -1;
}
/* Allocate DVB structure and start seek for devices */
dvb = dvb_dev_alloc();
if (!dvb) {
err("Can't allocate DVB data\n");
return -1;
}
dvb_dev_find(dvb, 0, NULL);
/* Create a socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
local_perror("socket");
goto error;
}
/* Initialize listen address struct */
memset((char *) &serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
/* Bind to the address */
ret = bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
if (ret < 0) {
local_perror("bind");
goto error;
}
/* FIXME: should allow the caller to set the verbosity */
dvb_dev_set_logpriv(dvb, 1, dvb_remote_log, &dvb_fd);
/* Listen up to 5 connections */
listen(sockfd, 5);
addrlen = sizeof(cli_addr);
start_signal_handler();
pthread_mutex_init(&msg_mutex, NULL);
pthread_mutex_init(&dvb_read_mutex, NULL);
/* Accept actual connection from the client */
warn("Support for Digital TV remote access is still highly experimental.\n"
"\nKnown issues:\n"
" - Abort needed to be implemented in a proper way;\n"
" - Need to make more stuff opaque, as touching on fields at the local\n"
" end is not automatically reflected remotely;\n"
" - The libdvbv5 API support is incomplete: it misses satellite, scan\n"
" and other functions;\n\n");
info(PROGRAM_NAME" started.");
while (1) {
int fd;
pthread_t id;
if (verbose)
dbg("waiting for connections");
fd = accept(sockfd, (struct sockaddr *)&cli_addr, &addrlen);
if (fd < 0) {
local_perror("accept");
break;
}
if (verbose)
dbg("accepted connection %d", fd);
ret = pthread_create(&id, NULL, start_server, (void *)&fd);
if (ret < 0) {
local_perror("pthread_create");
break;
}
}
/* Just in case we add some way for the remote part to stop the daemon */
stop_signal_handler();
error:
info(PROGRAM_NAME" stopped.");
pthread_exit(NULL);
if (dvb)
dvb_dev_free(dvb);
return -1;
}