Liseł Liseł - 2 months ago 11
C Question

C: Process `cd ..` and other commands on server application and return feedback to client

So I have got server/client application going on. So far I can communicate with the server quite well. I can get it to process

ls
command so the client gets the list of files. Here I have stumbled across a problem where I would like to type into server various commands like
cd ..
,
mv
,
cp
or
cd directory
so the server can process them and client would get a feedback on those actions. I thought that the code would go somewhere between those lines:

message_size = recv(socket_fd, input, sizeof(input)/sizeof(input[0]), 0);
if(message_size > 0) {
/* Process incoming message, add end of the line sign */
input[message_size] = '\0';

/* Process command and get a response from it */
fp = popen(input, "r");
if(fp == NULL)
fprintf(stderr, "popen() failed: %s\n", strerror(errno));
else {

// IS THIS THE RIGHT APPROACH??
if(strcmp(input, "ls") == 0) {
while((c = getc(fp)) != EOF) {
output[i] = c;
i++;
}
i = 0;
send_message(socket_fd, output);
}
else if(strcmp(input, "cd ..") == 0) {
send_message(socket_fd, "cd ..");
}
}

pclose(fp);


Quick explanation:
input
is what the server receives from the client. This can be
cd ..
.
send_message
is a function just to send some info to client - no big deal.
fp
is a type of
FILE *


How can I distiguish various commands, get a feedback from them and especially - get a feedback on badly inserted command from client like
asdf
. My thought was that using
strcmp
I could do various processing on the client but for some reason my client freezes when I type
ls
.

Client code:

#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>

#define MAX_SIZE 10000

int main(int argc, char* argv[])
{
int socket_fd, port_number;
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[MAX_SIZE];
int rc;

if(argc<2) {
fprintf(stderr,"Incorrect arguments input\n");
exit(0);
}

port_number = 12345;

server = gethostbyname(argv[1]);

socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd < 0) {
perror("Error opening socket");
exit(1);
}

bzero((char*) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port_number);
bcopy((char*) server->h_addr,(char *) &serv_addr.sin_addr.s_addr,sizeof(server->h_length));

if(connect(socket_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Error connecting");
exit(1);
}

enum {
INIT_STATE,
READY_STATE
} serv_state = INIT_STATE;

#define check_serv_state(str, st) \
(strncmp(str, #st, sizeof(#st) - 1) == 0)

for(;;) {
memset(buffer, 0, sizeof(buffer));
rc = recv(socket_fd, buffer, sizeof(buffer), 0);
if(rc<0) {
perror("Error reading back from server");
exit(1);
}
else if(rc == 0) {
printf("The server has been disconnected. Quitting.\n");
exit(0);
}

if(serv_state == INIT_STATE) {
char *part = strtok(buffer,"\n");
do {
if(check_serv_state(part, READY)) {
printf("Server is ready\n");
serv_state = READY_STATE;
}
else
printf("Server: [[[%s]]]\n", part);
} while((part = strtok(NULL, "\n")));

if(serv_state != READY_STATE)
continue;
} else
printf("#server: %s", buffer);

memset(buffer, 0, sizeof(buffer));
printf("#client: ");
fgets(buffer, sizeof(buffer)/sizeof(buffer[0]) - 1, stdin);

if(send(socket_fd, buffer, strlen(buffer), 0) < 0)
{
perror("Error sending message");
exit(1);
}
}

close(socket_fd);
return 0;
}


EDIT:

As @Olaf Dietsche said it was
fgets
fault. Now I am wondering why server does not process
cd ..
command.

Answer

Unless your server is sending some welcome message, the client "freezes", because it waits for input from the server

for (;;) {
    memset(buffer, 0, sizeof(buffer));
    rc = recv(socket_fd, buffer, sizeof(buffer), 0);

At the same time, the server is waiting for input from the client. So both are waiting for each other. That's the reason why there is no progress.


To fix this, you must first read input from the user, and send this input from the client to the server. E.g. move this from the end to the beginning of the loop

printf("#client: ");
fgets(buffer, sizeof(buffer)/sizeof(buffer[0]) - 1, stdin);

if (send(socket_fd, buffer, strlen(buffer), 0) < 0) {
    perror("Error sending message");
    exit(1);
}

Another gotcha might be fgets

Parsing stops if end-of-file occurs or a newline character is found, in which case str will contain that newline character.

Since you send the whole buffer, including the final newline, the server must check for this newline as well

if (strcmp(input, "ls\n") == 0) {

Alternatively, you may strip it before sending the request to the server

fgets(buffer, sizeof(buffer) / sizeof(buffer[0]) - 1, stdin);
int len = strlen(buffer);
if (buffer[len - 1] == '\n') {
    buffer[len - 1] = 0;
    --len;
}

if (send(socket_fd, buffer, len, 0) < 0) {

The cd command is special in the sense, that it changes the directory in the calling process only. When you use popen, the command will be processed by a child process

The popen() function opens a process by creating a pipe, forking, and invoking the shell.
...
The command argument is a pointer to a null-terminated string containing a shell command line. This command is passed to /bin/sh using the -c flag; interpretation, if any, is performed by the shell.

This means, the server calls popen, which creates another process, and this other process does the chdir.

If you want the server process change directories, you must not use popen, but do chdir in the server itself.

Comments