There are many bugs:
- In
terminal_thread
, input_command
is allocated on each loop iteration -- a memory leak
- Code to strip newline is broken
- With
.l
, not specifying an IP address causes a segfault because token
is NULL
- The port number in
terminal_thread
for .l
is 5126 which does not match the 9191 in the corresponding server code
- After connecting,
server_socket_file
does not do anything.
- In
server_socket_ls
, it loops on socket
, bind
, listen
, and accept
. The loop should start after the listen
(i.e. only do accept
in the loop and reuse the listening socket).
- Other bugs marked in the code
I had to refactor the code and add some debug. It is annotated with the bugs. I use cpp
conditionals to denote old vs. new code:
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
Here is the code. I got minimal .l
(remote ls) working:
Edit: Because of the update below running over SO space limits, I've elided the first code block I posted here.
Here is the debug.txt
output:
term term: PROMPT
term term: FGETS
ls ls: ENTER
ls ls: SOCKET
file file: ENTER
ls ls: BIND prtNum=9191
file file: BIND portNum=6123
ls ls: LISTEN
term term: COMMAND '.l'
term term: port=9191
ls ls: ACCEPTED
term term: PROMPT
This program is exiting as soon as its stops sending data at exit(0) and so doesn't ask for exit_status. Is there a way somehow to make it not stop and instead the terminal prompt reappears along with servers listening at the back? –
Dragut
Because I sensed the urgency, I erred on the side of a partial solution now is better than a perfect solution too late.
I may have introduced a bug with an extraneous exit
call in the ls server parent process (now fixed).
But, there are other issues ...
The main issue is that the server (for ls) is prompting the user whether to continue or not (on stdout/stdin
). This doesn't work too well.
It's the client (i.e. terminal_thread
) that should prompt the user. Or, as I've done it, the client will see exit
at the command prompt, then send a packet with "exit" in it to the server, and terminate. Then, the server will see this command and terminate.
I refactored as much as I could without completely redoing everything.
I split off some code into functions. Some of the can/could be reused to implement the "file" server.
But, I'd put both functions into a single server thread. I'd have the server look at the "command" it gets and do either of the actions based on the command. Since there's no code for actually doing something in the "file" server [yet] it's difficult to rework.
One thing to fix [which I did not have time for]:
The .l
command is of the form: .l [ip_address]
. The default for ip_address
is 127.0.0.1
. But, this should be split into two commands (e.g.):
- attach [ip_address]
- ls [ls arguments]
Anyway, here's the updated code. I had to move a bit quickly, so it's not quite as clean as I'd like.
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdarg.h>
#if 1
#include <time.h>
#endif
#define BACKLOG 10
#define MAX_SIZE 200
#define BACKLOG 10
#define BUFFSIZE 2048
#define MAXPENDING 5
#define MAX 2048
__thread char *tid;
__thread char dbgstrbuf[1000];
FILE *xfdbg;
double tsczero = 0.0;
typedef struct server_arg {
int portNum;
} server_arg;
typedef struct server_arg1 {
int portNum;
} server_arg1;
double
tscgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_MONOTONIC,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
sec -= tsczero;
return sec;
}
void
dbgprt(const char *fmt,...)
{
va_list ap;
char msg[1000];
char *bp = msg;
bp += sprintf(bp,"[%.9f/%4s] ",tscgetf(),tid);
va_start(ap,fmt);
bp += vsprintf(bp,fmt,ap);
va_end(ap);
fputs(msg,xfdbg);
}
const char *
dbgstr(const char *str,int len)
{
char *bp = dbgstrbuf;
if (len < 0)
len = strlen(str);
bp += sprintf(bp,"'");
for (int i = 0; i < len; ++i) {
int chr = str[i];
if ((chr > 0x20) && (chr <= 0x7E))
bp += sprintf(bp,"%c",chr);
else
bp += sprintf(bp,"{%2.2X}",chr);
}
bp += sprintf(bp,"'");
return dbgstrbuf;
}
void
setup(char inputBuffer[], char *args[], int *background)
{
const char s[4] = " \t\n";
char *token;
token = strtok(inputBuffer, s);
int i = 0;
while (token != NULL) {
args[i] = token;
i++;
// printf("%s\n", token);
token = strtok(NULL, s);
}
args[i] = NULL;
}
int
open_remote(const char *ip,unsigned short port)
{
int sock;
struct sockaddr_in echoserver;
dbgprt("open_remote: ENTER ip=%s port=%u\n",dbgstr(ip,-1),port);
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror("Failed to create socket");
exit(1);
}
int enable = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable,
sizeof(int)) < 0) {
perror("error");
}
memset(&echoserver, 0, sizeof(echoserver));
echoserver.sin_family = AF_INET;
echoserver.sin_addr.s_addr = inet_addr(ip);
// NOTE/BUG: this port number does _not_ match any server port
#if 0
echoserver.sin_port = htons(5126);
#else
dbgprt("term: port=%u\n",port);
echoserver.sin_port = htons(port);
#endif
if (connect(sock, (struct sockaddr *) &echoserver,
sizeof(echoserver)) < 0) {
perror("Failed to connect with server");
exit(1);
}
dbgprt("open_remote: EXIT sock=%d\n",sock);
return sock;
}
void *
terminal_thread(void *arg)
{
// NOTE/FIX: do this _once_
#if 1
char *input_command = malloc(MAX_SIZE);
#endif
tid = "term";
char buffer[BUFFSIZE];
int sock_ls = -1;
while (1) {
dbgprt("term: PROMPT\n");
printf(">> ");
//memset(input_command,0,strlen(str));
// NOTE/BUG: this is a memory leak
#if 0
char *input_command = malloc(MAX_SIZE);
#endif
dbgprt("term: FGETS\n");
fgets(input_command, MAX_SIZE, stdin);
// NOTE/BUG: code is broken to strip newline
#if 0
if ((strlen(input_command) > 0) &&
(input_command[strlen(input_command) - 1] == '\n'))
input_command[strlen(input_command) - 1] = '\0';
#else
input_command[strcspn(input_command,"\n")] = 0;
#endif
dbgprt("term: COMMAND %s\n",dbgstr(input_command,-1));
char list[] = "ls";
char cp[] = "cp";
#if 0
char s[100];
printf("%s\n", getcwd(s,100));
chdir("Desktop");
printf("%s\n", getcwd(s,100));
#endif
// exit program (and exit server)
if (strcmp(input_command,"exit") == 0) {
if (sock_ls >= 0) {
dbgprt("term: SENDEXIT\n");
if (send(sock_ls,"exit",4,0) < 0) {
perror("send/exit");
exit(1);
}
break;
}
}
if (strcmp(input_command, list) == 0) {
// ls code will run here
}
if ((input_command[0] == '.') && (input_command[1] == 'l')) {
printf("remote ls\n");
char ip[20];
const char c[2] = " ";
// strcpy(str,input_command);
char *token;
// get the first token
token = strtok(input_command, c);
// walk through other tokens
int i = 0;
while (token != NULL && i != -1) {
token = strtok(NULL, c);
i--;
}
#if 1
if (token == NULL) {
token = "127.0.0.1";
printf("no IP address found -- using %s\n",token);
}
#endif
if (sock_ls < 0)
sock_ls = open_remote(token,9191);
char s[100];
strcpy(s, "ls");
// NOTE/BUG: this blows away the "s" in "ls" because s is _set_ with strcpy
#if 0
s[strlen(s) - 1] = '\0'; // fgets doesn't automatically discard '\n'
#endif
unsigned int echolen;
echolen = strlen(s);
int received = 0;
/* send() from client; */
if (send(sock_ls, s, echolen, 0) != echolen) {
perror("Mismatch in number of sent bytes");
}
fprintf(stdout, "Message from server: ");
int bytes = 0;
/* recv() from server; */
if ((bytes = recv(sock_ls, buffer, echolen, 0)) < 1) {
perror("Failed to receive bytes from server");
}
received += bytes;
buffer[bytes] = '\0';
/* Assure null terminated string */
fprintf(stdout, buffer);
bytes = 0;
// this d {...} while block will receive the buffer sent by server
do {
buffer[bytes] = '\0';
printf("%s\n", buffer);
} while ((bytes = recv(sock_ls, buffer, BUFFSIZE - 1, 0)) >= BUFFSIZE - 1);
buffer[bytes] = '\0';
printf("%s\n", buffer);
printf("\n");
continue;
}
}
dbgprt("term: EXIT\n");
return (void *) 0;
}
int
ls_loop(int new_socket)
{
dbgprt("ls_loop: ENTER new_socket=%d\n",new_socket);
//code for ls
char buffer[BUFFSIZE];
int received = -1;
char data[MAX];
int stop = 0;
while (1) {
memset(data, 0, MAX);
// this will make server wait for another command to run until it
// receives exit
data[0] = '\0';
if ((received = recv(new_socket, buffer, BUFFSIZE, 0)) < 0) {
perror("Failed");
}
buffer[received] = '\0';
strcpy(data, buffer);
dbgprt("ls_loop: COMMAND %s\n",dbgstr(data,-1));
// this will force the code to exit
#if 0
if (strcmp(data, "exit") == 0)
exit(0);
puts(data);
#else
if (strncmp(data, "exit", 4) == 0) {
dbgprt("ls_loop: EXIT/COMMAND\n");
stop = 1;
break;
}
#endif
char *args[100];
setup(data, args, 0);
int pipefd[2], length;
if (pipe(pipefd))
perror("Failed to create pipe");
pid_t pid = fork();
char path[MAX];
if (pid == 0) {
// NOTE/BUG: no need to close before dup2
#if 0
close(1); // close the original stdout
#endif
dup2(pipefd[1], 1); // duplicate pipfd[1] to stdout
close(pipefd[0]); // close the readonly side of the pipe
close(pipefd[1]); // close the original write side of the pipe
execvp(args[0], args); // finally execute the command
exit(1);
}
if (pid < 0) {
perror("fork");
exit(1);
}
dbgprt("ls_loop: PARENT\n");
close(pipefd[1]);
while (length = read(pipefd[0], path, MAX - 1)) {
dbgprt("ls_loop: DATAREAD %s\n",dbgstr(path,length));
if (send(new_socket, path, length, 0) != length) {
perror("Failed");
}
memset(path, 0, MAX);
}
close(pipefd[0]);
}
dbgprt("ls_loop: EXIT stop=%d\n",stop);
}
void *
server_socket_ls(void *arg)
{
tid = "ls";
dbgprt("lsmain: ENTER\n");
do {
server_arg *s = (server_arg *) arg;
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
dbgprt("lsmain: SOCKET\n");
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
int enable = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable,
sizeof(int)) < 0) {
perror("error");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(s->portNum);
dbgprt("lsmain: BIND prtNum=%u\n",s->portNum);
if (bind(server_fd, (struct sockaddr *) &address, sizeof(address))
< 0) {
perror("bind failed");
}
dbgprt("lsmain: LISTEN\n");
if (listen(server_fd, 3) < 0) {
perror("listen");
}
while (1) {
if ((new_socket = accept(server_fd, (struct sockaddr *) &address,
(socklen_t *) & addrlen)) < 0) {
perror("accept");
}
dbgprt("lsmain: ACCEPTED\n");
int stop = ls_loop(new_socket);
close(new_socket);
if (stop) {
dbgprt("lsmain: STOP\n");
break;
}
}
} while (0);
dbgprt("lsmain: EXIT\n");
return (void *) 0;
}
void *
server_socket_file(void *arg)
{
tid = "file";
dbgprt("file: ENTER\n");
server_arg1 *s1 = (server_arg1 *) arg;
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
int enable = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int))
< 0) {
perror("error");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(s1->portNum);
dbgprt("file: BIND portNum=%u\n",s1->portNum);
if (bind(server_fd, (struct sockaddr *) &address, sizeof(address)) < 0) {
perror("bind failed");
}
if (listen(server_fd, 3) < 0) {
perror("listen");
}
if ((new_socket = accept(server_fd, (struct sockaddr *) &address,
(socklen_t *) & addrlen)) < 0) {
perror("accept");
}
printf("Server Connected\n");
}
int
main(int argc, char const *argv[])
{
tid = "main";
tsczero = tscgetf();
server_arg *s = (server_arg *) malloc(sizeof(server_arg));
server_arg1 *s1 = (server_arg1 *) malloc(sizeof(server_arg1));
pthread_t id_1;
pthread_t id_2;
pthread_t id_3;
xfdbg = fopen("debug.txt","w");
setlinebuf(xfdbg);
if (pthread_create(&id_3, NULL, terminal_thread, NULL) != 0) {
perror("pthread_create");
}
// NOTE/BUG: this port (or the one below) doesn't match the client code
// port of 5126
s->portNum = 9191;
pthread_create(&id_1, NULL, server_socket_ls, s);
s1->portNum = 6123;
if (0)
pthread_create(&id_2, NULL, server_socket_file, s1);
pthread_join(id_1, NULL);
if (0)
pthread_join(id_2, NULL);
pthread_join(id_3, NULL);
// NOTE/BUG: pthread_exit in main thread is wrong
#if 0
pthread_exit(0);
#else
fclose(xfdbg);
return 0;
#endif
}
UPDATE:
Feedback 2: the program does make terminal thread to reappear, but it doesn't listen anymore. When I tried to send ls command again from remote pc, it just blocks (and debugging shows it is because it gets stuck at blocking receive function). –
Dragut
I tried to avoid too much refactoring, but now, I've added more changes. This version is almost a complete rearchitecting:
pthread_create
is okay when testing, but isn't general enough if the server is on a different system.
- Usually, the client and server are separate programs (e.g. we start the server in a different window or from
systemd
).
- The server usually creates a subprocess/subthread to transfer the request (Below, I've done a
fork
but the server could do pthread_create
).
- This child process handles everything after the
accept
, so the server main process is free to loop on accept
and have multiple simultaneous clients.
- Because we're using stream sockets (e.g. TCP), each side needs to know when to stop reading. The usual is to create a
struct
that is a descriptor of the data to follow (e.g. xmsg_t
below) that has a "type" and a "payload length".
- Every bit of payload data that is sent/received is prefixed by such a descriptor.
- In other words, we need a simple "protocol"
Now, we need two windows (they can be on different systems):
- To start server:
./myprogram -s
- To start client:
./myprogram
Here's the refactored code. It is annotated:
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdbool.h>
#include <stdarg.h>
#if 1
#include <errno.h>
#include <time.h>
#include <sys/file.h>
#include <sys/wait.h>
#endif
#define MAXBUFF 2048 // max buffer size
#define MAXPENDING 5 // max number of connections (listen)
#define MAXARG 100 // max number of args
#define PORTNO 9191 // default port number
#if 0
#define STOP_SIGNO SIGTERM // stop signal to use
#else
#define STOP_SIGNO SIGHUP // stop signal to use
#endif
#define CLOSEME(_fd) \
do { \
dbgprt("CLOSEME fd=%d (" #_fd ")\n",_fd); \
if (_fd >= 0) \
close(_fd); \
_fd = -1; \
} while (0)
int opt_h; // 1=send HELO message
int opt_s; // 1=doserver, 0=doclient
int opt_n; // 1=run server command in foreground
char ipaddr[100] = { "127.0.0.1" };
unsigned short portno = PORTNO;
pid_t server_pid; // pid of server main process
volatile int server_signo; // signal received by server main
__thread char *tid;
__thread char dbgstrbuf[MAXBUFF + 1];
int dbgfd = -1;
double tsczero = 0.0;
typedef struct {
int xmsg_type;
int xmsg_paylen;
} xmsg_t;
enum {
XMSG_NOP,
XMSG_CMD,
XMSG_DATA,
XMSG_EOF,
};
double
tscgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_MONOTONIC,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
sec -= tsczero;
return sec;
}
#if _USE_ZPRT_
#ifndef DEBUG
#define DEBUG 1
#endif
#endif
#if DEBUG
#define dbgprt(_fmt...) \
xdbgprt(__FUNCTION__,_fmt)
#else
#define dbgprt(_fmt...) \
do { } while (0)
#endif
void
xdbgprt(const char *fnc,const char *fmt,...)
{
va_list ap;
char msg[MAXBUFF * 4];
char *bp = msg;
int sverr = errno;
bp += sprintf(bp,"[%.9f/%4s] %s: ",tscgetf(),tid,fnc);
va_start(ap,fmt);
bp += vsprintf(bp,fmt,ap);
va_end(ap);
// when doing forks, we have to lock the stream to guarantee atomic,
// non-interspersed messages that are sequential
flock(dbgfd,LOCK_EX);
lseek(dbgfd,0,2);
ssize_t remlen = bp - msg;
ssize_t curlen;
for (bp = msg; remlen > 0; remlen -= curlen, bp += curlen) {
curlen = write(dbgfd,bp,remlen);
if (curlen < 0) {
perror("xdbgprt");
break;
}
}
flock(dbgfd,LOCK_UN);
errno = sverr;
}
const char *
dbgstr(const char *str,int len)
{
char *bp = dbgstrbuf;
if (len < 0)
len = strlen(str);
bp += sprintf(bp,"'");
for (int i = 0; i < len; ++i) {
int chr = str[i];
if ((chr > 0x20) && (chr <= 0x7E))
bp += sprintf(bp,"%c",chr);
else
bp += sprintf(bp,"{%2.2X}",chr);
}
bp += sprintf(bp,"'");
return dbgstrbuf;
}
// tokenize -- convert buffer to tokens
int
tokenize(char **argv,const char *cmdbuf)
{
static char tokbuf[MAXBUFF];
char **av = argv;
strcpy(tokbuf,cmdbuf);
char *token = strtok(tokbuf," ");
while (token != NULL) {
*av++ = token;
token = strtok(NULL," ");
}
*av = NULL;
return (av - argv);
}
// xsend -- send buffer (guaranteed delivery)
ssize_t
xsend(int sock,const void *vp,size_t buflen,int flags)
{
const char *buf = vp;
ssize_t curlen;
ssize_t totlen = 0;
dbgprt("ENTER buflen=%zu flags=%8.8X\n",buflen,flags);
for (; totlen < buflen; totlen += curlen) {
dbgprt("LOOP totlen=%zd\n",totlen);
curlen = send(sock,&buf[totlen],buflen - totlen,flags);
if (curlen <= 0)
break;
}
dbgprt("EXIT totlen=%zd\n",totlen);
return totlen;
}
// xrecv -- receive buffer (guaranteed delivery)
ssize_t
xrecv(int sock,void *vp,size_t buflen,int flags)
{
char *buf = vp;
ssize_t curlen;
ssize_t totlen = 0;
dbgprt("ENTER buflen=%zu flags=%8.8X\n",buflen,flags);
for (; totlen < buflen; totlen += curlen) {
dbgprt("LOOP totlen=%zu\n",totlen);
curlen = recv(sock,&buf[totlen],buflen - totlen,flags);
if (curlen <= 0)
break;
}
dbgprt("EXIT totlen=%zd\n",totlen);
return totlen;
}
// open_remote -- client open connection to server
int
open_remote(const char *ip,unsigned short port)
{
int sock;
struct sockaddr_in echoserver;
dbgprt("ENTER ip=%s port=%u\n",dbgstr(ip,-1),port);
if ((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) {
perror("Failed to create socket");
exit(1);
}
// NOTE/BUG: only server (who does bind) needs to do this
#if 0
int enable = 1;
if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&enable,sizeof(enable)) < 0) {
perror("error");
}
#endif
memset(&echoserver,0,sizeof(echoserver));
echoserver.sin_family = AF_INET;
echoserver.sin_addr.s_addr = inet_addr(ip);
echoserver.sin_port = htons(port);
if (connect(sock,(struct sockaddr *) &echoserver,sizeof(echoserver)) < 0) {
perror("Failed to connect with server");
exit(1);
}
dbgprt("EXIT sock=%d\n",sock);
return sock;
}
// send_cmd -- client send command to server and process reply
void
send_cmd(int type,const char *cmd,int paylen)
{
int sock;
xmsg_t xmsg;
char buffer[MAXBUFF];
dbgprt("ENTER type=%d\n",type);
// open socket to remote server
sock = open_remote(ipaddr,portno);
// send command descriptor
xmsg.xmsg_type = type;
if (paylen < 0)
paylen = strlen(cmd);
xmsg.xmsg_paylen = paylen;
xsend(sock,&xmsg,sizeof(xmsg),0);
// send command payload
xsend(sock,cmd,xmsg.xmsg_paylen,0);
fprintf(stdout,"Message from server:\n");
int received = 0;
int bytes;
// get all data that the server sends back
while (1) {
dbgprt("LOOP\n");
// get descriptor for next chunk
xrecv(sock,&xmsg,sizeof(xmsg),0);
// handle EOF from server
if (xmsg.xmsg_paylen <= 0)
break;
// get payload
bytes = recv(sock,buffer,xmsg.xmsg_paylen,0);
dbgprt("RCVD bytes=%d\n",bytes);
#if 0
if (bytes == 0)
break;
#endif
/* recv() from server; */
if (bytes < 0) {
perror("Failed to receive bytes from server");
break;
}
received += bytes;
dbgprt("PAYLOAD %s\n",dbgstr(buffer,bytes));
// send payload to terminal
fwrite(buffer,1,bytes,stdout);
}
close(sock);
dbgprt("EXIT\n");
}
void
doclient(void)
{
char cmdbuf[MAXBUFF];
char *argv[MAXARG];
tid = "clnt";
while (1) {
dbgprt("PROMPT\n");
printf(">> ");
fflush(stdout);
dbgprt("FGETS\n");
fgets(cmdbuf,sizeof(cmdbuf),stdin);
cmdbuf[strcspn(cmdbuf,"\n")] = 0;
dbgprt("COMMAND %s\n",dbgstr(cmdbuf,-1));
// tokenize the line
int argc = tokenize(argv,cmdbuf);
if (argc <= 0)
continue;
// set/display remote server IP address
if (strcmp(argv[0],"remote") == 0) {
if (argc >= 2)
strcpy(ipaddr,argv[1]);
if (ipaddr[0] != 0)
printf("REMOTE: %s\n",ipaddr);
continue;
}
// stop server
if (strcmp(argv[0],"stop") == 0) {
if (ipaddr[0] != 0) {
dbgprt("STOP/SERVER\n");
send_cmd(XMSG_CMD,cmdbuf,-1);
}
ipaddr[0] = 0;
continue;
}
// exit client program
if (strcmp(argv[0],"exit") == 0) {
dbgprt("STOP/CLIENT\n");
break;
}
// send command and echo response to terminal
send_cmd(XMSG_CMD,cmdbuf,-1);
}
dbgprt("EXIT\n");
}
// server_cmd -- process command on server
void
server_cmd(int new_socket)
{
xmsg_t xmsg;
char cmdbuf[MAXBUFF];
char *argv[MAXARG];
dbgprt("ENTER new_socket=%d\n",new_socket);
do {
// get command descriptor
xrecv(new_socket,&xmsg,sizeof(xmsg),0);
// get command text
xrecv(new_socket,cmdbuf,xmsg.xmsg_paylen,0);
cmdbuf[xmsg.xmsg_paylen] = 0;
dbgprt("COMMAND %s\n",dbgstr(cmdbuf,-1));
// tokenize the command
int argc = tokenize(argv,cmdbuf);
if (argc <= 0)
break;
// stop the server
if (strcmp(argv[0],"stop") == 0) {
dbgprt("KILL server_pid=%d\n",server_pid);
// FIXME -- we could send a "stopping server" message here
// send EOF to client
xmsg.xmsg_type = XMSG_EOF;
xmsg.xmsg_paylen = 0;
xsend(new_socket,&xmsg,sizeof(xmsg),0);
// signal the server main process to stop (cleanly)
if (opt_s)
server_signo = STOP_SIGNO;
else
kill(server_pid,STOP_SIGNO);
break;
}
int pipefd[2];
int length;
if (pipe(pipefd))
perror("Failed to create pipe");
pid_t pid = fork();
dbgprt("FORK pid=%d\n",pid);
// invoke the target program (under a pipe)
if (pid == 0) {
tid = "exec";
dbgprt("DUP2\n");
fflush(stdout);
int err = dup2(pipefd[1],1); // duplicate pipefd[1] to stdout
if (err < 0)
perror("dup2");
CLOSEME(pipefd[0]); // close the readonly side of the pipe
CLOSEME(pipefd[1]); // close the write side of the pipe
dbgprt("EXECVP\n");
CLOSEME(dbgfd);
if (opt_h) {
int len = sprintf(cmdbuf,"HELO\n");
write(1,cmdbuf,len);
}
execvp(argv[0],argv); // finally execute the command
perror("execvp");
exit(1);
}
// fork error
if (pid < 0) {
perror("fork");
exit(1);
}
dbgprt("PARENT\n");
CLOSEME(pipefd[1]);
// grab all output from the target program and send in packets to
// client
while (1) {
dbgprt("READBEG\n");
length = read(pipefd[0],cmdbuf,sizeof(cmdbuf));
dbgprt("READEND length=%d\n",length);
if (length < 0) {
perror("readpipe");
break;
}
if (length == 0)
break;
dbgprt("READBUF %s\n",dbgstr(cmdbuf,length));
// send descriptor for this chunk
xmsg.xmsg_type = XMSG_DATA;
xmsg.xmsg_paylen = length;
xsend(new_socket,&xmsg,sizeof(xmsg),0);
// send the payload
if (xsend(new_socket,cmdbuf,length,0) != length) {
perror("Failed");
}
}
CLOSEME(pipefd[0]);
// tell client we have no more data
xmsg.xmsg_paylen = 0;
xmsg.xmsg_type = XMSG_EOF;
xsend(new_socket,&xmsg,sizeof(xmsg),0);
} while (0);
CLOSEME(new_socket);
dbgprt("EXIT\n");
}
void
sighdr(int signo)
{
server_signo = signo;
}
void
doserver(void)
{
int server_fd,new_socket;
struct sockaddr_in address;
pid_t pid;
tid = "serv";
dbgprt("ENTER\n");
server_pid = getpid();
#if 0
signal(STOP_SIGNO,(void *) sighdr);
#else
struct sigaction act;
sigaction(STOP_SIGNO,NULL,&act);
act.sa_sigaction = (void *) sighdr;
sigaction(STOP_SIGNO,&act,NULL);
sigset_t set;
sigemptyset(&set);
sigaddset(&set,STOP_SIGNO);
sigprocmask(SIG_UNBLOCK,&set,NULL);
#endif
#if 0
int addrlen = sizeof(address);
#else
socklen_t addrlen = sizeof(address);
#endif
dbgprt("SOCKET\n");
// Creating socket file descriptor
if ((server_fd = socket(AF_INET,SOCK_STREAM,0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
int enable = 1;
if (setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&enable,sizeof(int)) < 0) {
perror("error");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(portno);
dbgprt("BIND portno=%u\n",portno);
if (bind(server_fd,(struct sockaddr *) &address,sizeof(address)) < 0) {
perror("bind failed");
}
dbgprt("LISTEN\n");
if (listen(server_fd,MAXPENDING) < 0) {
perror("listen");
}
int pending = 0;
int status;
while (1) {
dbgprt("LOOP\n");
// reap all finished children
while (1) {
pid = waitpid(-1,&status,WNOHANG);
if (pid <= 0)
break;
dbgprt("REAP pid=%d pending=%d\n",pid,pending);
--pending;
}
// one of the children was given a stop command and it signaled us
if (server_signo) {
dbgprt("SIGNO server_signo=%d\n",server_signo);
break;
}
// wait for new connection from a client
// FIXME -- sending us a signal to stop cleanly is _broken_ because
// we do _not_ get an early return here (e.g. EINTR) -- we may need
// select with timeout
dbgprt("WAITACCEPT\n");
new_socket = accept(server_fd,(struct sockaddr *) &address,
(socklen_t *) &addrlen);
// stop cleanly
if (server_signo) {
dbgprt("SIGNO server_signo=%d\n",server_signo);
break;
}
if (new_socket < 0) {
if (errno == EINTR)
break;
perror("accept");
}
dbgprt("ACCEPTED\n");
// do command execution in main process (i.e. debug)
if (opt_n) {
server_cmd(new_socket);
continue;
}
pid = fork();
if (pid < 0) {
CLOSEME(new_socket);
continue;
}
// process the command in the child
if (pid == 0) {
server_cmd(new_socket);
exit(0);
}
++pending;
dbgprt("CHILD pid=%d\n",pid);
// server main doesn't need this after fork
#if 1
CLOSEME(new_socket);
#endif
}
// reap all children
while (pending > 0) {
pid = waitpid(-1,&status,0);
if (pid <= 0)
break;
dbgprt("REAP pid=%d pending=%d\n",pid,pending);
--pending;
}
dbgprt("EXIT\n");
}
int
main(int argc,char **argv)
{
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch (cp[-1]) {
case 'h':
opt_h = ! opt_h;
break;
case 'n': // do _not_ fork server
opt_n = ! opt_n;
break;
case 'p':
portno = (*cp != 0) ? atoi(cp) : PORTNO;
break;
case 's': // invoke server
opt_s = ! opt_s;
break;
}
}
tsczero = tscgetf();
#if DEBUG
int flags = O_WRONLY | O_APPEND;
if (opt_s)
flags |= O_TRUNC | O_CREAT;
dbgfd = open("debug.txt",flags,0644);
if (dbgfd < 0) {
perror("debug.txt");
exit(1);
}
#endif
if (opt_s)
doserver();
else
doclient();
#if DEBUG
if (dbgfd >= 0)
close(dbgfd);
#endif
return 0;
}