[Redbutton-devel] SF.net SVN: redbutton: [5] redbutton-download/trunk
Brought to you by:
skilvington
|
From: <ski...@us...> - 2006-03-01 16:05:42
|
Revision: 5 Author: skilvington Date: 2006-03-01 08:05:30 -0800 (Wed, 01 Mar 2006) ViewCVS: http://svn.sourceforge.net/redbutton/?rev=5&view=rev Log Message: ----------- allow rb-download to listen for commands from rb-browser Modified Paths: -------------- redbutton-download/trunk/Makefile redbutton-download/trunk/TODO redbutton-download/trunk/rb-download.c redbutton-download/trunk/utils.c redbutton-download/trunk/utils.h Added Paths: ----------- redbutton-download/trunk/command.c redbutton-download/trunk/command.h redbutton-download/trunk/listen.c redbutton-download/trunk/listen.h Modified: redbutton-download/trunk/Makefile =================================================================== --- redbutton-download/trunk/Makefile 2006-03-01 10:26:41 UTC (rev 4) +++ redbutton-download/trunk/Makefile 2006-03-01 16:05:30 UTC (rev 5) @@ -4,6 +4,8 @@ OBJS= rb-download.o \ list.o \ findmheg.o \ + listen.o \ + command.o \ assoc.o \ carousel.o \ module.o \ Modified: redbutton-download/trunk/TODO =================================================================== --- redbutton-download/trunk/TODO 2006-03-01 10:26:41 UTC (rev 4) +++ redbutton-download/trunk/TODO 2006-03-01 16:05:30 UTC (rev 5) @@ -1,3 +1,5 @@ +replace TRUE/FALSE with stdbool + work out why this happened on BBC1 once: read: Value too large for defined data type Unable to read PID Added: redbutton-download/trunk/command.c =================================================================== --- redbutton-download/trunk/command.c (rev 0) +++ redbutton-download/trunk/command.c 2006-03-01 16:05:30 UTC (rev 5) @@ -0,0 +1,185 @@ +/* + * command.c + */ + +#include <stdio.h> +#include <string.h> +#include <stdbool.h> + +#include "command.h" +#include "utils.h" + +/* max number of args that can be passed to a command (arbitrary) */ +#define ARGV_MAX 10 + +/* the commands */ +bool cmd_file(struct listen_data *, int, int, char **); +bool cmd_help(struct listen_data *, int, int, char **); +bool cmd_quit(struct listen_data *, int, int, char **); + +static struct +{ + char *name; + char *args; + bool (*proc)(struct listen_data *, int, int, char **); + char *help; +} command[] = +{ + { "exit", "", cmd_quit, "Kill the programme" }, + { "file", "<ContentReference>", cmd_file, "Retrieve the given file from the carousel" }, + { "help", "", cmd_help, "List available commands" }, + { "quit", "", cmd_quit, "Kill the programme" }, + { NULL, NULL, NULL, NULL } +}; + +/* + * process the given command + * return true if we should quit the programme + */ + +bool +process_command(struct listen_data *listen_data, int client_sock, char *cmd) +{ + int argc; + char *argv[ARGV_MAX]; + char term; + unsigned int cmd_len; + int i; + + /* chop it into words, complicated by quoting */ + argc = 0; + while(argc < ARGV_MAX && *cmd != '\0') + { + argv[argc++] = cmd; + /* do we need to find the next space, or the next quote */ + if(*cmd == '\'' || *cmd == '"') + { + term = *cmd; + /* remove/skip the opening quote */ + argv[argc-1] ++; + cmd ++; + } + else + { + /* stop at the next space */ + term = ' '; + } + /* find the next terminating character */ + while(*cmd != term && *cmd != '\0') + cmd ++; + /* if this is not the end, skip to the start of the next word */ + if(*cmd != '\0') + { + /* terminate the last word */ + *cmd = '\0'; + /* move onto the next non-space character */ + cmd = skip_ws(cmd + 1); + } + } + + cmd_len = strlen(argv[0]); + for(i=0; command[i].name != NULL; i++) + { + if(strncmp(argv[0], command[i].name, cmd_len) == 0) + return (command[i].proc)(listen_data, client_sock, argc, argv); + } + + write_string(client_sock, "500 Unrecognised command\n"); + + return false; +} + +/* + * the commands + * listen_data is global data needed by listener commands + * client_sock is where any response data should go + * argc is the number of arguments passed to it + * argv[0] is the command name (or the abreviation the user used) + * return true if we should quit the programme + */ + +/* + * file <ContentReference> + * send the given file down client_sock + * ContentReference should be absolute, ie start with "~//" + */ + +bool +cmd_file(struct listen_data *listen_data, int client_sock, int argc, char *argv[]) +{ + char *filename; + FILE *file; + size_t nread; + char buff[1024 * 8]; + + if(argc != 2) + { + write_string(client_sock, "500 Syntax: file <ContentReference>\n"); + return false; + } + + filename = argv[1]; + + /* is ContentReference absolute */ + if(strlen(filename) < 3 || strncmp(filename, "~//", 3) != 0) + { + write_string(client_sock, "500 ContentReference is not absolute\n"); + return false; + } + + /* strip off the ~// prefix */ + filename += 3; + + if((file = fopen(filename, "r")) == NULL) + { + write_string(client_sock, "404 Not found\n"); + return false; + } + + write_string(client_sock, "200 OK\n"); + + do + { + nread = fread(buff, 1, sizeof(buff), file); + write_all(client_sock, buff, nread); + } + while(nread == sizeof(buff)); + + fclose(file); + + return false; +} + +/* + * help + */ + +bool +cmd_help(struct listen_data *listen_data, int client_sock, int argc, char *argv[]) +{ + int i; + char name_args[64]; + char help_line[128]; + + write_string(client_sock, "200 OK\n"); + + for(i=0; command[i].name != NULL; i++) + { + snprintf(name_args, sizeof(name_args), "%s %s", command[i].name, command[i].args); + snprintf(help_line, sizeof(help_line), "%-30s %s\n", name_args, command[i].help); + write_string(client_sock, help_line); + } + + return false; +} + +/* + * quit + */ + +bool +cmd_quit(struct listen_data *listen_data, int client_sock, int argc, char *argv[]) +{ + return true; +} + Added: redbutton-download/trunk/command.h =================================================================== --- redbutton-download/trunk/command.h (rev 0) +++ redbutton-download/trunk/command.h 2006-03-01 16:05:30 UTC (rev 5) @@ -0,0 +1,12 @@ +/* + * command.h + */ + +#ifndef __COMMAND_H__ +#define __COMMAND_H__ + +#include "listen.h" + +bool process_command(struct listen_data *, int, char *); + +#endif Added: redbutton-download/trunk/listen.c =================================================================== --- redbutton-download/trunk/listen.c (rev 0) +++ redbutton-download/trunk/listen.c 2006-03-01 16:05:30 UTC (rev 5) @@ -0,0 +1,249 @@ +/* + * listen.c + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <signal.h> +#include <stdbool.h> +#include <errno.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "command.h" +#include "utils.h" + +/* listen() backlog, 5 is max for BSD apparently */ +#define BACKLOG 5 + +/* internal functions */ +static int get_host_addr(char *, struct in_addr *); + +static bool handle_connection(struct listen_data *, int, struct sockaddr_in *); +static void dead_child(int); + +/* + * extract the IP addr and port number from a string in one of these forms: + * host:port + * ip-addr:port + * host + * ip-addr + * port + * if the string is NULL, or the host or the port are not defined in the string, + * the corresponding param passed to this routine is not changed + * ip and port are both returned in network byte order + * returns -1 on error (can't resolve host name) + */ + +int +parse_addr(char *str, struct in_addr *ip, in_port_t *port) +{ + char *p; + int ishost; + + /* easy case */ + if(str == NULL) + return 0; + + if((p = strchr(str, ':')) != NULL) + { + /* its either host:port or ip:port */ + *(p++) = '\0'; + if(get_host_addr(str, ip) < 0) + return -1; + *port = htons(atoi(p)); + /* reconstruct the string */ + *(--p) = ':'; + } + else + { + /* its either host, ip, or port */ + /* all digits => a port number */ + ishost = 0; + for(p=str; *p && !ishost; p++) + ishost = !isdigit(*p); + if(ishost) + { + if(get_host_addr(str, ip) < 0) + return -1; + } + else + { + *port = htons(atoi(str)); + } + } + + return 0; +} + +/* + * puts the IP address associated with the given host into output buffer + * host can be a.b.c.d or a host name + * returns 0 if successful, -1 on error + */ + +static int +get_host_addr(char *host, struct in_addr *output) +{ + struct hostent *he; + int error = 0; + + if(((he = gethostbyname(host)) != NULL) && (he->h_addrtype == AF_INET)) + memcpy(output, he->h_addr, sizeof(struct in_addr)); + else + error = -1; + + return error; +} + +/* + * start a process to listen on the given interface for commands from a remote rb-browser + */ + +void +start_listener(struct listen_data *listen_data) +{ + struct sigaction action; + pid_t child; + int sockopt; + int listen_sock; + int accept_sock; + fd_set read_fds; + socklen_t addr_len; + struct sockaddr_in client_addr; + bool quit; + + /* + * fork: + * the parent listens for commands, + * the child returns and downloads the carousel + */ + + /* don't let our children become zombies */ + action.sa_handler = dead_child; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + if(sigaction(SIGCHLD, &action, NULL) < 0) + fatal("signal: SIGCHLD: %s", strerror(errno)); + + /* if we can't fork it's probably best to kill ourselves*/ + if((child = fork()) < 0) + fatal("fork: %s", strerror(errno)); + /* child returns */ + else if(child == 0) + return; + /* parent continues */ + + /* listen on the given ip:port */ + printf("Listening on %s:%u\n", inet_ntoa(listen_data->addr.sin_addr), ntohs(listen_data->addr.sin_port)); + + if((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) + fatal("socket: %s", strerror(errno)); + + /* in case someones already using it */ + sockopt = 1; + if(setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)) < 0) + fatal("setsockopt: SO_REUSEADDR: %s", strerror(errno)); + + if(bind(listen_sock, (struct sockaddr *) &listen_data->addr, sizeof(struct sockaddr_in)) < 0) + fatal("bind: %s", strerror(errno)); + + if(listen(listen_sock, BACKLOG) < 0) + fatal("listen: %s", strerror(errno)); + + /* listen for connections */ + while(true) + { + FD_ZERO(&read_fds); + FD_SET(listen_sock, &read_fds); + if(select(listen_sock + 1, &read_fds, NULL, NULL, NULL) < 0) + { + /* could have been interupted by SIGCHLD */ + if(errno != EINTR) + error("select: %s", strerror(errno)); + continue; + } + /* check select didnt fuck up */ + if(!FD_ISSET(listen_sock, &read_fds)) + continue; + addr_len = sizeof(client_addr); + if((accept_sock = accept(listen_sock, (struct sockaddr *) &client_addr, &addr_len)) < 0) + { + /* we get ECONNABORTED in Linux if we're being SYN scanned */ + error("accept: %s", strerror(errno)); + continue; + } + /* fork off a child to handle it */ + if((child = fork()) < 0) + { + /* if we can't fork it's probably best to kill ourselves*/ + fatal("fork: %s", strerror(errno)); + } + else if(child == 0) + { + /* child */ + close(listen_sock); + quit = handle_connection(listen_data, accept_sock, &client_addr); + close(accept_sock); +/* TODO */ +if(quit) printf("QUIT!!!\n"); + exit(EXIT_SUCCESS); + } + else + { + /* parent */ + close(accept_sock); + } + } + + /* we never get here */ + close(listen_sock); + + return; +} + +/* + * handle a connection from a remote rb-browser + */ + +static bool +handle_connection(struct listen_data *listen_data, int client_sock, struct sockaddr_in *client_addr) +{ + char cmd[1024]; + ssize_t nread; + bool quit = false; + + printf("Connection from %s:%d\n", inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port)); + + /* read the command from the client */ + if((nread = read(client_sock, cmd, sizeof(cmd)-1)) > 0) + { + /* \0 terminate the buffer */ + cmd[nread] = '\0'; + /* strip off an trailing \n (only needed when testing with telnet etc) */ + nread --; + while(nread > 0 && (cmd[nread] == '\n' || cmd[nread] == '\r')) + cmd[nread--] = '\0'; + /* process the command */ + quit = process_command(listen_data, client_sock, cmd); + } + + printf("Connection from %s:%d closed\n", inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port)); + + return quit; +} + +static void +dead_child(int signo) +{ + if(signo == SIGCHLD) + wait(NULL); + + return; +} Added: redbutton-download/trunk/listen.h =================================================================== --- redbutton-download/trunk/listen.h (rev 0) +++ redbutton-download/trunk/listen.h 2006-03-01 16:05:30 UTC (rev 5) @@ -0,0 +1,19 @@ +/* + * listen.h + */ + +#ifndef __LISTEN_H__ +#define __LISTEN_H__ + +#include <netinet/in.h> + +struct listen_data +{ + struct sockaddr_in addr; /* ip:port to listen on */ +}; + +int parse_addr(char *, struct in_addr *, in_port_t *); + +void start_listener(struct listen_data *); + +#endif Modified: redbutton-download/trunk/rb-download.c =================================================================== --- redbutton-download/trunk/rb-download.c 2006-03-01 10:26:41 UTC (rev 4) +++ redbutton-download/trunk/rb-download.c 2006-03-01 16:05:30 UTC (rev 5) @@ -1,5 +1,5 @@ /* - * rb-download [-d <demux_device>] [-b <base_dir>] [-t <timeout>] [-c <carousel_id>] [<service_id>] + * rb-download [-d <demux_device>] [-b <base_dir>] [-t <timeout>] [-l[<listen-addr>]] [-c <carousel_id>] [<service_id>] * * Download the DVB Object Carousel for the given channel onto the local hard disc * files will be stored under the current dir if no -b option is given @@ -7,6 +7,17 @@ * if no service_id is given, a list of possible channels (and their service_id) is printed * the carousel ID is normally read from the PMT, use -c to explicitly set it * + * the default timeout is 10 seconds + * if no DSMCC data is read after this time, it is assumed none is being broadcast + * + * if -l is given, rb-download listens on the network for commands from a remote rb-browser + * the default IP to listen on is 0.0.0.0 (ie all interfaces), the default TCP port is 10101 + * listen-addr should be given in the form "host:port", where host defaults to 0.0.0.0 and port defaults to 10101 + * eg, to listen on a different port, do "-l8080" + * to only listen on the loop back, do "-l127.0.0.1" or on a different port too, do "-l127.0.0.1:8080" + * NOTE: because -l may or may not take an argument, you must not put a space between the -l and the value + * (otherwise, "rb-download -l 1234", is ambiguous - listen on port 1234 or use service_id 1234?) + * * the file structure will be: * ./services/<service_id> * this is a symlink to the root of the carousel @@ -17,7 +28,7 @@ */ /* - * Copyright (C) 2005, Simon Kilvington + * Copyright (C) 2005, 2006, Simon Kilvington * * 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 @@ -39,11 +50,14 @@ #include <stdio.h> #include <stdlib.h> #include <stdint.h> +#include <stdbool.h> #include <errno.h> +#include <netinet/in.h> #include "list.h" #include "findmheg.h" #include "carousel.h" +#include "listen.h" #include "utils.h" /* DVB demux device */ @@ -52,6 +66,10 @@ /* seconds before we assume no DSMCC data is available on this PID */ #define DEFAULT_TIMEOUT 10 +/* default listen address */ +#define DEFAULT_LISTEN_ADDR INADDR_ANY +#define DEFAULT_LISTEN_PORT 10101 + void usage(char *); int @@ -60,12 +78,14 @@ char *prog_name = argv[0]; char *device = DEFAULT_DEVICE; unsigned int timeout = DEFAULT_TIMEOUT; + bool listen = false; + struct listen_data listen_data; int carousel_id = -1; uint16_t service_id; struct carousel *car; int arg; - while((arg = getopt(argc, argv, "d:b:t:c:")) != EOF) + while((arg = getopt(argc, argv, "d:b:t:l::c:")) != EOF) { switch(arg) { @@ -82,6 +102,16 @@ timeout = strtoul(optarg, NULL, 0); break; + case 'l': + listen = true; + /* default values */ + listen_data.addr.sin_addr.s_addr = htonl(DEFAULT_LISTEN_ADDR); + listen_data.addr.sin_port = htons(DEFAULT_LISTEN_PORT); + /* optarg is NULL if no value is given, parse_addr can't fail with NULL */ + if(parse_addr(optarg, &listen_data.addr.sin_addr, &listen_data.addr.sin_port) < 0) + fatal("Unable to resolve host %s", optarg); + break; + case 'c': carousel_id = strtoul(optarg, NULL, 0); break; @@ -101,6 +131,8 @@ service_id = strtoul(argv[optind], NULL, 0); car = find_mheg(device, timeout, service_id, carousel_id); printf("Carousel ID=%u\n", car->carousel_id); + if(listen) + start_listener(&listen_data); load_carousel(car); } else @@ -114,6 +146,11 @@ void usage(char *prog_name) { - fatal("Usage: %s [-d <demux_device>] [-b <base_dir>] [-t <timeout>] [-c carousel_id] [<service_id>]", prog_name); + fatal("Usage: %s [-d <demux_device>] " + "[-b <base_dir>] " + "[-t <timeout>] " + "[-l[<listen-addr>]] " + "[-c carousel_id] " + "[<service_id>]", prog_name); } Modified: redbutton-download/trunk/utils.c =================================================================== --- redbutton-download/trunk/utils.c 2006-03-01 10:26:41 UTC (rev 4) +++ redbutton-download/trunk/utils.c 2006-03-01 16:05:30 UTC (rev 5) @@ -20,13 +20,76 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stdarg.h> +#include <string.h> #include <ctype.h> +#include <errno.h> #include "utils.h" +/* + * write the given string (ie upto \0) to the given fd + */ + +void +write_string(int fd, const char *str) +{ + write_all(fd, str, strlen(str)); + + return; +} + +/* + * guarantee writing count bytes from buf to fd + * W. Richard Stevens + */ + +void +write_all(int fd, const void *buf, size_t count) +{ + size_t nwritten; + const char *buf_ptr; + + buf_ptr = buf; + while(count > 0) + { + if((nwritten = write(fd, buf_ptr, count)) < 0) + { + if(errno == EINTR || errno == EAGAIN) + nwritten = 0; + else + fatal("write: %s\n", strerror(errno)); + } + count -= nwritten; + buf_ptr += nwritten; + } + + return; +} + +/* + * move str to the next non-white space character (or the end of the string) + */ + +char * +skip_ws(char *str) +{ + if(str == NULL) + return NULL; + + while(*str != '\0' && isspace((int) *str)) + str ++; + + return str; +} + +/* + * returns a single ASCII char for the values 0-15 + */ + char hex_digit(uint8_t val) { Modified: redbutton-download/trunk/utils.h =================================================================== --- redbutton-download/trunk/utils.h 2006-03-01 10:26:41 UTC (rev 4) +++ redbutton-download/trunk/utils.h 2006-03-01 16:05:30 UTC (rev 5) @@ -36,6 +36,11 @@ #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif +void write_string(int, const char *); +void write_all(int, const void *, size_t); + +char *skip_ws(char *); + char hex_digit(uint8_t); void *safe_malloc(size_t); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |