help with unix command exec1 in C

if i run exec1 to execute a command such as ls; ls, then is there a way i can get a return value? If i am using pipes and the child process runs the exec1 line, how can the parent process know the return value of the result of exec1?

Do you mean exec? There is a shell comand in section 1 of the manual (man pages).

Or are you trying to get the C code to create a child process whith fork() and execl()?
I am guessing you want this:
Try the system() C call, then use the macros in sys/wait.h to get your return code.

This has been answered serveral times:

I'm guessing you mean execl.

If it finds the program at all, the execl call never returns because execl tells your program to replace itself with the program you created. So naturally it can't return anything directly.

Using the waitpid() call you can get a process' return statistics, like

int status;
waitpid(childpid, &status, 0);
printf("child returned %d\n", WEXITSTATUS(status));

You should always wait() for your children, if not immediately then some time or other, since they can't truly quit until you do -- just hang around taking up space in the process table.

so if i have a command and i want a child process to execute it and return the return value to the parent process, can i use:
will return_value get the return value of the command ls; ls ?

the man of system says:
system() executes a command specified in command by calling /bin/sh -c
command, and returns after the command has been completed. During exe�
cution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT
will be ignored.

i dont understand how to return after the command has been completed...

no; that ends up as part of the status variable, which you can get with WEXITSTATUS(status).

I don't know what you expected to read from fd[0] after you closed it anyway.

fileno(stdin) is redundant. stdin is always 0. Use STDIN_FILENO (from unistd.h) if you want to be symbolic.

system() does so by itself.

so now will this get the return value of the execl process?

Have you actually tried any of this code?

It might, if the amount of data is small enough. But the child might end up waiting forever for you to read the pipes, so, if you're not going to use the data the child printed to the pipe, close() them before you wait().

i think theres something wrong with this code
since in the child process, it runs execl, so the child process gets replaced by ls, ls command process, so when it finishes where does the return number go to? does it go to the parent process (the fd[0] end of the pipe)? And how does Wexitstatus get the return code of the command?

Have you actually tried any of this code?

The return code goes to wait (or waitpid) as part of the status variable. The kernel actually keeps the process around in "zombie" form until you wait for it, which is how it can get the statistics even after it's dead. It's only part of the status variable which is why you need WEXITSTATUS to extract it.

Ok it sort of works now, but theres a problem, here what i did
The output is sorta correct, i gave the input command to be ls, the output was
my_prog.c
my_prog
abc
��
E���8���*�
0

The zero come up since ls was successfull, but i dont understand why the files in the directory came up line by line and not all in one line like how normally doing ls would result in, also theres some gibberish lines after the ls command results, why did that show up? Am I missing a \0 anywhere or something?

Also i tried giving a command that would fail like cd ffg, and the WEXITSTATUS number came out to be 2, so what really are the possible outcomes for WEXITSTATUS? i only know so far 0 and 2, shouldnt it be -1 instead of 2?

ls can tell the difference between a terminal and a pipe. ls | cat will do that too. In other words -- that's normal.

Remember that read() doesn't null-terminate for you -- it's a general purpose system call that can be used for anything, not just strings. So you're not only missing a NULL -- you're mistaken in that you even need NULLs.

Also, you shouldn't do printf(string); like that, since that could cause crashes if your string ends up having formatting specifiers like %s in it -- printf will try to read more parameters off the stack and crash since there aren't any. Let's use write() instead -- it needs no NULLs and can't crash in that manner.

I have no idea why you're duplicating pipes in the parent's code when you don't ever use the duplicate. Don't just throw dup2() around, only use it when you know exactly what you want to do with it why.

Furthermore, I repeat: fileno(stdin) is redundant. Use STDIN_FILENO.

Further furthermore: read first, then wait(). And keep reading until it's done, otherwise you might be throwing away tons of output.

Further further furthermore: You're still not closing your pipes before you wait(). This can cause a deadlock if the child process thinks it's waiting for you because it'll wait until the pipe closes. You could even end up waiting for yourself when you're reading from one end and still have the other end open.

int sys_new(char *comm) {
    char pipeout[512];
    int fd[2], status;
    pipe(fd);
    pid_t pid = fork();
    if (pid == 0) { 
        dup2(fd[1], STDOUT_FILENO);
        close(fd[0]); close(fd[1]);
        execl("/bin/sh", "/bin/sh", "-c", comm, NULL);
        // Need to exit() down here in case execl fails
        perror("couldn't execl");
        exit(1);
    }
    else if (pid > 0) {
        ssize_t bytes;
        close(fd[1]); // So we don't wait for ourselves forever.
     
        bytes=read(fd[0], pipeout, 512);
        while(bytes > 0) // happens when child's pipe closes.
        {
            write(STDOUT_FILENO, pipeout, bytes);            
            bytes=read(fd[0], pipeout, 512);
        }
        close(fd[0]);

        wait(&status);
    }
    else {
        perror("fork"); exit(1);
    }
    return 0;
}

Possible return values are 0 through 255, it can't return negative numbers. 0 means success, anything else means error, what the return values mean specifically depends on the application.

1 Like

when I run it using ls, i get

my_prog.c
my_prog
abc
my_prog.c
my_prog
abc
ٷEi�8i��ط
0

Why is this "ٷEi�8i��ط" coming up when i do printf, I dont understand what you mean by adding NULL's. How can i printf it while making the output of what i want to only come up, and not that gibberish stuff?

Now I know you don't try the code I post for you, my version doesn't do that.

It's because, as stated above, read() doesn't add NULLs for you. This causes printf() to write more data than you actually read, because it doesn't know when to end.

Also as above, read() shouldn't add NULLs for you -- NULLs only make sense in C text strings, which program output might not always be. So you should write exactly the bytes you got, and exactly as many bytes as you read, nothing more, nothing less. This will work for any kind of data, and is what the code I posted above does.

1 Like

i dont see where in your version are u recording the amount of bytes written, and then using that number to read that amount...

this line reads

bytes=read(fd[0], pipeout, 512);
and it reads 512 bytes which i think is more than needed, how do i know the amount written from execl?

It happens here:

bytes=read(fd[0], pipeout, 512);

'bytes' gets set to the number of bytes it was able to read, or 0 on EOF, see 'man 2 read'.

512 is just a maximum. It reads as much as it can up to the maximum and tells you where it stopped. You don't need to know the exact amount the child wrote -- you can read its output in as big or as small chunks as you want, though you don't always get what you asked for. Check the return value of read() to find out what you actually got.

You may not get the exact amount that the child wrote, by the way, since the pipe is also a buffer. The child could do ten writes of 10 bytes and you get one read of 100 bytes; up to a few kilobytes of data may pile up in the pipe before it actually lets anything through. It flushes when the writing end closes, which is why you're still able to get less (and why you need to close the writing end if you're not using it.)

so to correctly output the result i need this command?

printf(pipeout, bytes);

EDIT: i just tried that and i still get the gibberish line...

For the third time, don't use printf. printf expects NULL-terminated C strings and raw data read from a file is almost never a NULL-terminated C string.

To output properly, use the code I gave you:

This is not pseudocode. read() and write() are system calls. All of that is necessary, including the loop, to properly handle the program's output, otherwise it might stop before the program's actually finished.

ok i see now, but i have a question about stdin

when i run the program using something like
./my_prog ls; ls; ls

argc is only equal to 2, and not 4, why is this? I want the input ls; ls; ls to be accessed in my program...

; is a special character to the shell. a ; b ; c means "run a, then run b, then run c". If you want to feed raw ;'s into something you have to quote them. ./my_prog ls';' ls';' ls The shell will strip off the quotes before it calls ./my_prog

i tried the command was cd abc';' ls
but i am still in the same directory, is this because the child process ran the exec1 command? if so then if i reverse it so the parent does what the child did and child does what the parent did, then will the cd abc';' ls actually move it to the abc directory?