0

I am writing a basic shell in c that will allow me to execute simple commands(I am not asked to check for optional arguments) like "ls" in a server (localhost). The program must be able to handle multiple clients.

I have done everything up to the part I have to execute the command with execve() (I MUST USE THIS FUNCION). I've found that execve() returns -1 on failure and that it doesn't return anything on success so I am making a fork() to execute the command in that process.

Now, to the problem. How do I know if execve() executed successfully? I can't seem to find the problem, my code always returns "OK" to the client. "csapp.h" it's just a source file containing wrappers around some functions.

#include "csapp.h"

void echo(int connfd, pid_t pid);

int main(int argc, char **argv)
{
    int listenfd, connfd;
    unsigned int clientlen;
    struct sockaddr_in clientaddr;
    struct hostent *hp;
    char *haddrp, *port;
    pid_t pid;

    if (argc != 2) {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(0);
    }
    port = argv[1];

    listenfd = Open_listenfd(port);
    while (1) {
        clientlen = sizeof(clientaddr);
        while(1){
            connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
            if((pid=Fork())==-1){
                Close(connfd);
            }
            if(pid > 0){
                break;
            }
        }

        /* Determine the domain name and IP address of the client */
        hp = Gethostbyaddr((const char *)&clientaddr.sin_addr.s_addr,
                    sizeof(clientaddr.sin_addr.s_addr), AF_INET);
        haddrp = inet_ntoa(clientaddr.sin_addr);
        printf("server connected to %s (%s)\n", hp->h_name, haddrp);

        echo(connfd, pid);
        Close(connfd);
    }
    exit(0);
}

void trim(char *string){
    string[strlen(string)-1]=0;

}

char* concat(const char *s1, const char *s2)
{
    char *result = malloc(strlen(s1) + strlen(s2) + 1); // +1 for the null-terminator
    // in real code you would check for errors in malloc here
    strcpy(result, s1);
    strcat(result, s2);
    return result;
}

void echo(int connfd, pid_t pid)
{
    size_t n;
    char buf[MAXLINE];
    rio_t rio;

    char *args[2];

    args[1] = NULL;

    Rio_readinitb(&rio, connfd);
    while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
        trim(buf);
        args[0] = concat("/bin/", buf);
        printf("server received %lu bytes\n", n);
        printf("Command: %s\n",buf);
        pid_t execPID;
        int status;
        if((execPID = fork()) > pid){
            execve(args[0],args,NULL);
        }else{

            wait(&status);
            if(WIFEXITED(status)){
                if (WEXITSTATUS(status) == 0){
                    printf("status: %d\n", status);
                    printf("WIFEXITED: %d\n", WIFEXITED(status));
                    printf("WEXITSTATUS: %d\n", WEXITSTATUS(status));
                        Rio_writen(connfd, "OK\n", 3); 
                }
                else
                        Rio_writen(connfd, "ERROR\n", 6);

            }
        }
        /*if(status == -1){
                Rio_writen(connfd, "ERROR\n", 6);

        }
            else{
                Rio_writen(connfd, "OK\n", 3);
                printf("%d\n", status);
            }*/


    }

}

Output for "m" and "ls" sent by the client:

server received 2 bytes
Command: m
status: 0
WIFEXITED: 1
WEXITSTATUS: 0
server received 3 bytes
Command: ls
status: 0
WIFEXITED: 1
WEXITSTATUS: 0
Makefile   client    csapp.c  csapp.o  server.c
README.md  client.c  csapp.h  server

I'd really appreciate some help, I've been stuck at this for the past 14 hours.

Johnny Beltran
  • 701
  • 2
  • 8
  • 22
  • So you begin in C and you want to make a shell server... that a lot. Your question is not clear, too broad, and don't have a [mcve]. Read [ask] – Stargateur Aug 10 '18 at 02:10
  • `wait(&status);` you didn't look the error status of `wait()`, plus don't use `wait()` it's "obsolete", use `waitpid()` to be sure to wait the good child. – Stargateur Aug 10 '18 at 02:18
  • 3
    `if((execPID = fork()) > pid)` don't make any sense, you should verify that `fork()` is a success and then just compare it to `0` to now if it's a child, as you did in your first `fork()` – Stargateur Aug 10 '18 at 02:20
  • @Stargateur wait(&status) and waitpid(-1,&status,0) always returns -1, no matter if the command ran successfully or didn't at all – Johnny Beltran Aug 10 '18 at 02:47
  • ["wait(): on success, returns the process ID of the terminated child; on error, -1 is returned. waitpid(): on success, returns the process ID of the child whose state has changed; if WNOHANG was specified and one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned. On error, -1 is returned."](http://man7.org/linux/man-pages/man2/waitpid.2.html), Are you **sure** ? Look the error so ! – Stargateur Aug 10 '18 at 02:50
  • @Stargateur Output: server received 3 bytes Command: ls Check: -1 status: 0 WIFEXITED: 1 WEXITSTATUS: 0 Makefile client csapp.c csapp.o server.c README.md client.c csapp.h server server received 7 bytes Command: asdasd execve failed: No such file or directory Check: -1 status: 0 WIFEXITED: 1 WEXITSTATUS: 0 – Johnny Beltran Aug 10 '18 at 02:58
  • Look the error, mean look `errno`, use `perror("wait():");` for a print it. – Stargateur Aug 10 '18 at 03:02
  • @Stargateur I know wait returns the pid of the terminated child, then that means wait and waitpid cannot retrieve de pid of the child process, why? – Johnny Beltran Aug 10 '18 at 03:02
  • @Stargateur Ok, I added perror("wait::") after the line wait(&status) and output is: "wait():: No child processes", so I guess fork is not working? – Johnny Beltran Aug 10 '18 at 03:08
  • I think it's because it's not the parent who run `wait()` do a proper `fork()` as I said in my third comment. – Stargateur Aug 10 '18 at 03:10
  • @Stargateur Ok, I'm lost, I put this instead of previous if statement and still doesn't work: if((execPID = fork()) == -1){ close(connfd); } if(execPID > 0){ execve(args[0],args,NULL); perror( "execve failed" ); exit( EXIT_FAILURE); – Johnny Beltran Aug 10 '18 at 03:25
  • 1
    You doing it wrong, it's the child who must execute the command, so when `execPID` is `0`, not when `> 0` – Stargateur Aug 10 '18 at 03:27
  • @Stargateur DUDE I OWE YOU MY LIFE, I could've sworn that pid = 0 when it's the parent proces, THANKS! I want to mark this as the right answer – Johnny Beltran Aug 10 '18 at 03:39

3 Answers3

2

How do I know if execve() executed successfully?

In the first place, as you've already observed, the server knows that the execve() call itself has failed if it returns at all.

In the second place, after the child process terminates, wait() or waitpid() can tell you what its exit status was. You seem to have that worked out, too.

So when you go on to say,

I can't seem to find the problem, my code always returns "OK" to the client.

, that leads me to believe that your actual question is more along the lines of "how do I get the command's output?" Answer: via the child's standard output and (possibly) standard error streams. How else?

By default, the child inherits its standard input, standard output, and standard error from its parent, but you can give it different ones by calling dup2() in the child process before execve(). You can do a variety of fairly sophisticated things with that by connecting the child's streams to pipe()s that the parent or some other process can read, but the simplest thing to do would be to use dup2() to simply direct the child's output and error streams to the socket:

// standard output:
if (dup2(connfd, 1) == -1) { /* handle error */ }

// standard error:
if (dup2(connfd, 2) == -1) { /* handle error */ }

Then the command's output, if any, will be sent directly over the wire to the client.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
1

this line:

{
    execve(args[0],args,NULL);
}

should be changed to:

{
    execve( args[0],args,NULL );
    perror( "execve failed" );
    exit( EXIT_FAILURE );
}

Then, the code does not continue to execute if the call to execve() fails AND if it fails, tells you why.

Note: both exit() and EXIT_FAILURE are from the header file: stdlib.h

user3629249
  • 16,402
  • 1
  • 16
  • 17
  • I added those lines and now the server outputs execve failed: No such file or directory when I type a command that doesn't exist, but it keeps sending "OK" to the client. wait(&status) and waitpid(-1,&status,0) return -1 always no matter if the command ran successfully or didn't at all. Why is this happening? Shouldn't they return -1 only on failure because that's when the subprocess actually sends an exit status? – Johnny Beltran Aug 10 '18 at 02:54
  • @JohnnyBeltran: If the command is `ls`, you'll only execute it correctly if the process is in the `/bin` directory (assuming that `ls` is found as `/bin/ls`). If you write `/bin/ls` as the command, then you will be able to run it elsewhere. There's an `execvp()` function that searches the directories listed in `$PATH` (the environment variable); when you don't use that (or `execvpe()` if that's available), then there is no path-based search for the executable. – Jonathan Leffler Aug 10 '18 at 05:45
1

As @Stargateur said in the comments below the question, I was doing wrong the comparison of execPID. Basically, pid is positive when it's the parent process and zero when it's the child process, so changing if ((execPID = fork()) > pid) to if ((execPID = fork()) == 0) actually solves the problem.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Johnny Beltran
  • 701
  • 2
  • 8
  • 22