1

The following code (in the end) represents thread function which takes in ls command from remote client and send current working directory to that client.

It successfully sends but there is one issue: When it stops sending completely, I want it to start listening again. At line:

printf("Enter 1 if you want to exit or 0 if you don't: "); 
                 fgets(exit_status,MAX_SIZE,stdin);

It gets stuck and it is terminated (and starts another thread) when I press Enter

Which I don't understand why? and while I was debugging I saw above print statement executes after pressing Enter despite the debugger being at the end of function (means it passed this print statement).

I want it to start listening again automatically when it finish sending data.

If anyone wants to look at my full code here is the link:https://pastebin.com/9UmTkPge

void  *server_socket_ls(void *arg) {
    int* exit_status = (int*)malloc(sizeof(int));
    *exit_status = 0;   
    while (*exit_status == 0) {
    //socket code is here
        
    //code for ls
        
    char buffer[BUFFSIZE];
    int received = -1;
    char data[MAX];
    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';
    
        strcat (data,  buffer);
        if (strcmp(data, "exit")==0) // this will force the code to exit
            exit(0);
    
        puts (data);
            char *args[100];
            setup(data,args,0);
            int pipefd[2],lenght;
    
            if(pipe(pipefd))
                perror("Failed to create pipe");
    
            pid_t pid = fork();
            char path[MAX];
    
            if(pid==0)
            {
                close(1); // close the original stdout
                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
            }
            else
                if(pid>0)
                {
                    close(pipefd[1]);
                    memset(path,0,MAX);
    
                    while(lenght=read(pipefd[0],path,MAX-1)){
                        printf("Data read so far %s\n", path);
                        if(send(new_socket,path,strlen(path),0) != strlen(path) ){
                            perror("Failed");
                        }
                        //fflush(NULL);
                        printf("Data sent so far %s\n", path);
                        memset(path,0,MAX);
                        
                    }
    
                    close(pipefd[0]);
                    //removed so server will not terminate
                              
                }
                else 
                {
                    printf("Error !\n");
                    exit(0);
                }
                 printf("Enter 1 if you want to exit or 0 if you don't: "); 
                 fgets(exit_status,MAX_SIZE,stdin);    
                
        }
    
    }
Dragut
  • 107
  • 8

1 Answers1

4

There are many bugs:

  1. In terminal_thread, input_command is allocated on each loop iteration -- a memory leak
  2. Code to strip newline is broken
  3. With .l, not specifying an IP address causes a segfault because token is NULL
  4. The port number in terminal_thread for .l is 5126 which does not match the 9191 in the corresponding server code
  5. After connecting, server_socket_file does not do anything.
  6. 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).
  7. 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.):

  1. attach [ip_address]
  2. 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:

  1. pthread_create is okay when testing, but isn't general enough if the server is on a different system.
  2. Usually, the client and server are separate programs (e.g. we start the server in a different window or from systemd).
  3. The server usually creates a subprocess/subthread to transfer the request (Below, I've done a fork but the server could do pthread_create).
  4. This child process handles everything after the accept, so the server main process is free to loop on accept and have multiple simultaneous clients.
  5. 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".
  6. Every bit of payload data that is sent/received is prefixed by such a descriptor.
  7. In other words, we need a simple "protocol"

Now, we need two windows (they can be on different systems):

  1. To start server: ./myprogram -s
  2. 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;
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • 1
    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 May 22 '22 at 23:43