You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

375 lines
8.0 KiB

/*
Copyright Jeroen Vreeken (pe1rxq@amsat.org), 2007, 2008
Copyright Stichting C.A. Muller Radioastronomiestation, 2007, 2008
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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include <limits.h>
#include <shell/shell.h>
#include <log/log.h>
struct cmdlist {
struct shell_cmd *cmd;
struct cmdlist *next;
};
static struct cmdlist *list = NULL;
static int listen_port = 0;
#define CLIENT_BUFSIZE 4096
char *shell_intro = "Command Shell";
char *shell_prompt = ">";
enum {
SHELL_NEW,
SHELL_OUTPUT,
SHELL_OUTPROMPT,
SHELL_INPUT,
};
struct client {
int free;
int sock;
int state;
int quit;
bool quoted;
char inbuf[CLIENT_BUFSIZE];
int insize;
char outbuf[CLIENT_BUFSIZE];
int outsize;
};
static int nr_shell_hdl;
static struct client *shell_hdl = NULL;
int shell_cmd_add(struct shell_cmd *cmd)
{
struct cmdlist *new_entry;
struct cmdlist **entryp;
new_entry = malloc(sizeof(struct cmdlist));
if (!new_entry)
return -1;
new_entry->cmd = cmd;
new_entry->next = NULL;
for (entryp = &list; *entryp; entryp = &(*entryp)->next);
*entryp = new_entry;
log_send(LOG_T_DEBUG, "Added '%s' command to the shell", cmd->cmd);
return 0;
}
void shell_cmd(struct client *shell)
{
char *tok;
char *args;
char *sptr = NULL;
struct cmdlist *entry;
shell->outsize = 0;
if (shell->insize == 0) {
return;
}
tok = strtok_r(shell->inbuf, " ", &sptr);
args = strtok_r(NULL, "", &sptr);
if (!tok) {
shell->outsize = sprintf(shell->outbuf, "\n");
return;
}
if (!strcmp(tok, "help")) {
shell->outsize = sprintf(shell->outbuf,
"%s\nCommands:\n\n"
"\thelp\tHelp\n"
"\tquit\tQuit\n",
shell_intro);
for (entry = list; entry; entry = entry->next) {
shell->outsize +=
sprintf(shell->outbuf + shell->outsize,
"\t%s\t%s\n", entry->cmd->cmd,
entry->cmd->helptext);
}
return;
} else if (!strcmp(tok, "quit")) {
shell->outsize = sprintf(shell->outbuf,
"Closing connection\n");
shell->quit = 1;
return;
} else {
for (entry = list; entry; entry = entry->next) {
if (!strcmp(tok, entry->cmd->cmd)) {
shell->outsize = CLIENT_BUFSIZE;
entry->cmd->handler(
args,
shell->outbuf,
&shell->outsize);
return;
}
}
}
if (tok == NULL)
tok = "";
shell->outsize = sprintf(shell->outbuf,
"ERROR: unknown command '%s'. Type 'help' for available commands\n",
tok);
}
static void *shell_handle(void *arg)
{
int i, high;
fd_set fdset_r, fdset_w;
struct timeval tv;
while (1) {
high = 0;
FD_ZERO(&fdset_r);
FD_ZERO(&fdset_w);
for (i = 0; i < nr_shell_hdl; i++) {
if (shell_hdl[i].free)
continue;
if (shell_hdl[i].state == SHELL_NEW) {
ioctl(shell_hdl[i].sock, FIONBIO, &(int){1});
shell_hdl[i].outsize = sprintf(
shell_hdl[i].outbuf,
"%s\n", shell_intro);
shell_hdl[i].state = SHELL_OUTPUT;
}
if (shell_hdl[i].outsize) {
FD_SET(shell_hdl[i].sock, &fdset_w);
if (shell_hdl[i].sock > high)
high = shell_hdl[i].sock;
} else if (shell_hdl[i].state == SHELL_INPUT) {
FD_SET(shell_hdl[i].sock, &fdset_r);
if (shell_hdl[i].sock > high)
high = shell_hdl[i].sock;
}
}
tv.tv_sec = 0;
tv.tv_usec = 100000;
if (high)
select(high + 1, &fdset_r, &fdset_w, NULL, &tv);
else
usleep(tv.tv_usec);
for (i = 0; i < nr_shell_hdl; i++) {
if (shell_hdl[i].free)
continue;
if (shell_hdl[i].outsize) {
int ret;
ret = write(shell_hdl[i].sock,
shell_hdl[i].outbuf,
shell_hdl[i].outsize);
if (ret != shell_hdl[i].outsize &&
errno != EAGAIN) {
close(shell_hdl[i].sock);
shell_hdl[i].free = 1;
} else {
shell_hdl[i].outsize = 0;
switch(shell_hdl[i].state) {
case SHELL_OUTPUT:
if (shell_hdl[i].quit) {
shell_hdl[i].free = 1;
close(shell_hdl[i].sock);
}
shell_hdl[i].state = SHELL_OUTPROMPT;
shell_hdl[i].outsize =
sprintf(shell_hdl[i].outbuf,
"%s", shell_prompt);
break;
case SHELL_OUTPROMPT:
shell_hdl[i].state = SHELL_INPUT;
break;
}
}
} else if (shell_hdl[i].state == SHELL_INPUT) {
int ret;
do {
ret = read(shell_hdl[i].sock,
shell_hdl[i].inbuf +
shell_hdl[i].insize,
1);
if (ret == 1) {
char *c =
&shell_hdl[i].inbuf[shell_hdl[i].insize];
if (*c == '\r') {
/* nop */
} else if (*c == '\'') {
shell_hdl[i].quoted =
!shell_hdl[i].quoted;
} else if (*c == '\n' && !shell_hdl[i].quoted) {
*c = 0;
shell_cmd(&shell_hdl[i]);
shell_hdl[i].insize = 0;
shell_hdl[i].state = SHELL_OUTPUT;
} else {
shell_hdl[i].insize++;
if (shell_hdl[i].insize>=
CLIENT_BUFSIZE) {
shell_hdl[i].insize = 0;
}
}
}
} while (ret == 1);
if (ret <= 0 && errno != EAGAIN) {
close(shell_hdl[i].sock);
shell_hdl[i].free = 1;
}
} else if (shell_hdl[i].state == SHELL_OUTPUT) {
shell_hdl[i].state = SHELL_OUTPROMPT;
shell_hdl[i].outsize = sprintf(
shell_hdl[i].outbuf,
"%s", shell_prompt);
}
}
}
return NULL;
}
static void *shell_server(void *arg)
{
int fd_accept;
struct sockaddr_in sockaddr;
signal(SIGPIPE, SIG_IGN);
fd_accept = socket(AF_INET, SOCK_STREAM, 0);
if (fd_accept < 0) {
log_send(LOG_T_ERROR, "shell: socket() failed: %d", errno);
return NULL;
}
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockaddr.sin_port = htons(listen_port);
setsockopt(fd_accept, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
if (bind(fd_accept,
(struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
log_send(LOG_T_ERROR, "shell: bind() failed: %d", errno);
close(fd_accept);
return NULL;
}
if (listen(fd_accept, 4) < 0) {
log_send(LOG_T_ERROR, "shell: listen() failed: %d", errno);
close(fd_accept);
return NULL;
}
while (1) {
int client;
struct sockaddr_in sockaddr_client;
socklen_t len;
len = sizeof(sockaddr_client);
client = accept(fd_accept, (struct sockaddr *)&sockaddr_client,
&len);
if (client >= 0) {
int i;
for (i = 0; i < nr_shell_hdl; i++) {
if (shell_hdl[i].free) {
shell_hdl[i].sock = client;
shell_hdl[i].state = SHELL_NEW;
shell_hdl[i].quit = 0;
shell_hdl[i].quoted = 0;
shell_hdl[i].free = 0;
break;
}
if (i == nr_shell_hdl) {
close(client);
continue;
}
}
}
}
return NULL;
}
int shell_server_start(int port, int clients)
{
pthread_t thread_id;
pthread_attr_t attr;
int i;
listen_port = port;
nr_shell_hdl = clients;
shell_hdl = malloc(sizeof(struct client) * clients);
if (!shell_hdl)
return -1;
for (i = 0; i < nr_shell_hdl; i++) {
shell_hdl[i].free = 1;
}
log_send(LOG_T_DEBUG,
"Starting shell server with a maximum of %d clients on port %d",
clients, port);
pthread_attr_init(&attr);
#ifndef __FreeBSD__
pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN * 2);
#endif
pthread_create(&thread_id, &attr, shell_server, NULL);
pthread_create(&thread_id, &attr, shell_handle, NULL);
return 0;
}