My program should use fork and exec system calls. The exec should change the child process such that it takes another command as an argument and executes
that command. For example, to display the message of the day:
./myexec cat /etc/motd
This is my current code
extern char **environ; /* environment info */
main(int argc, char **argv) {
/* argc -- number of arguments */
/* argv -- an array of strings */
char *argvNew[argc + 1];
int pid;
for(int i=0; i<argc; i++){
argvNew[i] = argv[i];
}
argvNew[argc + 1] = NULL;
printf("After For: %s\n",argvNew[0]);
printf("After For: %s\n",argvNew[1]);
printf("After For: %s\n",argvNew[2]);
printf("After For: %s\n",argvNew[3]);
if ((pid = fork()) < 0) {
fprintf(stderr, "Fork error%sstrerror\n", strerror(errno));
exit(1);
}
else if (pid == 0) {
/* child process */
if (execve(argvNew[0], argvNew, environ) < 0) {
fprintf(stderr, "Execve error%d %s\n",errno,strerror(errno));
exit(1);
}
}
else {
/* parent */
wait(0); /* wait for the child to finish */
}
}
After running ./myexecv cat etc/motd nothing happens; just the print statements. Any advice going forward?
There are multiple bugs in the shown code.
for(int i=0; i<argc; i++){
argvNew[i] = argv[i];
}
argvNew[argc+1] = NULL;
On it's face value, the NULL assignment is wrong, and will result in undefined behavior because argvNew is declared as
char *argvNew[argc + 1];
So the array contains values argvNew[0] through argvNew[argc], and argvNew[argc+1]=NULL; runs off past the end of the array, that results in undefined behavior. This should obviously be
argvNew[argc] = NULL;
But even that would also be wrong, because:
execve(argvNew[0], argvNew, environ);
argvNew[0] is copied from argv[0], which is the name of this program being executed. This is going to fork and run the same program, in the child process.
You will end up forkbombing yourself. If this is a shared server, you will make the system administrator very very mad.
You need to remove argv[0] from the equation, and copy over only argv[1], and on. The correct loop, and copy, is:
int pid;
char *argvNew[argc];
for(int i=1; i<argc; i++){
argvNew[i-1] = argv[i];
}
argvNew[argc-1] = NULL;
The calling parameters of execve() require the first parameter to be the filename to execute. Unfortunately you pass it argvNew[0] which is the same value as argv[0]. This means that you call and call again your own program and never the script. You need to shift the parameters by one:
...
for(int i=1; i<argc; i++){
argvNew[i-1] = argv[i];
}
argvNew[argc-1] = NULL;
...
Related
I am creating a custom shell and currently working on getting multiple piping to work. Eg, ls -al | wc -l returns the number of all file directories in current directory. I am closely following the "solution code" at this link.
Here is my implementation, with very similar command input structure:
// every command is a
// struct command
// with their arguments attached
struct command
{
char **cmd; // argument list for execvp(eg: {"ls", "-al"})
int numArgs; // number of total arguments in current line
};
// my implementation of multiple pipes based on link supplied with slight modifications
void runPipedCommands(struct command *commands)
{
// this code just counds the number of pipes in commands
int numPipes = 0;
for (int i = 0; i < commands->numArgs; i++)
{
if (!strcmp(commands[i].cmd[0], "|"))
numPipes++;
}
printf("number of pipes: %d\n", numPipes);
int status;
int i = 0;
pid_t pid;
int pipefds[2 * numPipes];
// create the pipes
for (i = 0; i < (numPipes); i++)
{
if (pipe(pipefds + i * 2) < 0)
{
perror("couldn't pipe");
exit(EXIT_FAILURE);
}
}
int j = 0;
int c = 0;
while (commands[c].cmd)
{
// skip the pipe operators
if (!strcmp(commands[c].cmd[0], "|"))
c++;
pid = fork();
if (pid == 0)
{
printf("cmd to execute: %s\n", commands[c].cmd[0]);
//if not last command
if (commands[c + 1].cmd[0])
{
// everyone even fd gets this, set stdout to write
// end of pipe
if (dup2(pipefds[j + 1], STDOUT_FILENO) < 0)
{
perror("dup2");
exit(EXIT_FAILURE);
}
}
else
{
exit(EXIT_SUCCESS);
}
//if not first command&& j!= 2*numPipes
if (j != 0)
{
// every odd fd gets this, set stdin to
// read end of pipe
if (dup2(pipefds[j - 2], STDIN_FILENO) < 0)
{
perror(" dup2");
exit(EXIT_FAILURE);
}
}
// close dup2ed fds
for (i = 0; i < 2 * numPipes; i++)
{
close(pipefds[i]);
}
// execvp
if (execvp(commands[c].cmd[0], commands[c].cmd) < 0)
{
perror(commands[c].cmd[0]);
exit(EXIT_FAILURE);
}
}
else if (pid < 0)
{
perror("error");
exit(EXIT_FAILURE);
}
c++;
j += 2;
}
/**Parent closes the pipes and wait for children*/
for (i = 0; i < 2 * numPipes; i++)
{
close(pipefds[i]);
}
for (i = 0; i < numPipes + 1; i++)
wait(&status);
}
// my main function with my test input:
int main(int argc, char *argv[])
{
// my parseW function simply returns a list of struct commands
struct command *results = parseW("ls -al | wc -l");
runPipedCommands(results);
return 0;
}
The above code with parseW omitted results in a terminal output of:
Everything looks correct in the execution of the pipes, and I've gone over the file descriptors (there are only 2 to account for in my specific test case). I'm not sure what I'm doing wrong.
Without posting parseW(), the content of a commands[] array is unknown.
Within runPipedCommands() while loop, there are 3 areas that need revisions:
Provided a commands[x].cmd[] may privately contain a pipe, there's nothing to execute
and no need to fork() and exit; instead add a continue :
// skip the pipe operators
if (!strcmp(commands[c].cmd[0], "|")) {
c++;
continue;
}
After a fork(), the child process tries to test the next element of
commands[], assumes that member .cmd is non-NULL and deferences
with .cmd[0]; provided the last element of commands[] contains a NULL .cmd,
the child would crash here:
if (commands[c + 1].cmd[0])
instead, update the test without the dereference:
if (commands[c + 1].cmd)
Related with the added continue above, when this updated .cmd test is false, there's no need to exit and prevent
the child from reaching the execvp(); delete this block:
else {
exit(EXIT_SUCCESS);
}
Also: when the parent calls wait(), the status can be checked with
macros and indicate whether the child terminated normally, see a
wait manual
I have a C program that computes the next prime number after 100, I need to execute this program using execlp function. The prime number program and execlp function are both different files but are in the same directory. Any help is appreciated, below is my code for execlp function and my C program. I've already tried running the program using execlp, but it doesn't seem to work. I've included the path, the program to run and NULL as arguments.
//Main program, that runs execlp function
int main(int argc, char *argv[]) {
int pid;
int start=0;
pid = fork(); /* fork a child process */
if(pid > 0) { /* parent continues here */
count(start, 'P');
wait(NULL); /* to get printing done before shell prompt */
} else if(pid == 0) { /* child got here */
execlp("/home/student/Documents/FIT2100/PRAC3/", "./primeNum",
NULL); /* execute primeNum program*/
} else { /* there is a problem with fork */
perror("Failed to fork a process\n");
exit(1);
}
}
//Prime number program, filename is called primeNum.c
int main() {
bool isPrime = false;
int counter = 0;
int inputNum;
inputNum = 100;
while(isPrime == false){
inputNum += 1;
for(int i = 1; i <= inputNum; i++){
if(inputNum % i == 0){
counter++;
}
}
if(counter <= 2){
isPrime = true;
}
}
printf("Next prime after 100 is %d\n", inputNum);
}
The 'path' argument to execl() is the pathname of the executable, not the directory containing the executable. Making a guess, you probably need:
execlp("/home/student/Documents/FIT2100/PRAC3/primeNum", "primeNum", (char *)NULL);
The first argument is the absolute pathname of the program; the second is the string that will passed as argv[0]; the NULL (which should strictly be (char *)NULL or (char *)0 to avoid possible problems with #define NULL 0 — as shown) marks the end of the arguments. There's nothing to stop you using "pink elephant" as the argv[0] value if you want to.
A relative pathname is perfectly acceptable; if an open() call would find the executable file using the name, then it is OK for use with execl().
I am trying to write a simple shell program. I take a user input via getline(), and tokenize that string - I keep the arguments in a char* arg[]; execvp, however, never works and always returns a neg. number.
CODE:
char* arg[100];
int i = 0;
for (i; i<100; i++){
arg[i] = malloc(11); //each one has 11 bytes
}
tokenize();
i = 0;
for (i; i<100; i++){
printf("Arg %d: %s\n", i, arg[i]);
}
pid_t child = -5;
child = fork();
if (child == 0){
if (execvp(arg[0], arg) < 0){
printf("Failed.\n");
fflush(stdout);
}
}
RUNNING:
ls
OUTPUT:
Arg 0: ls
Arg 1:
Arg 2:
....
Failed.
I'm not sure why this is happening. Any tips would be greatly appreciated.
I am so close to figuring out a program I have been writing for linux shell written in C. I have been wanting to get this working for a while now, and I decided to pick it up again and have been tinkering with it for the past few weeks.
For the following code, keep in mind that the array called arrayOfCommands is dynamically filled. My code fills the arrayOfCommands with the current command being run. For the sake of my example, we will be running the command ls -l | wc and arrayOfCommands is filled with the following, depending on which time through the loop it is:
//Pass #1
arrayOfCommands[]= ("ls", "-l", NULL)
//Pass #2
arrayOfCommands[]= ("wc", NULL)
Here is what I have so far:
//PIPING
int do_command(char **args, int pipes) {
// pipes is the number of pipes in the command
// (In our example, one)
// The number of commands is one more than the
// number of pipes (In our example, two)
const int commands = pipes + 1; //Ex: 2
int i = 0;
// Set up the pipes
int pipefds[2*pipes];
for(i = 0; i < pipes; i++){
if(pipe(pipefds + i*2) < 0) {
perror("Couldn't Pipe");
exit(EXIT_FAILURE);
}
}
// Variables
int pid;
int status;
char *str_ptr;
int j = 0;
for (i = 0; i < commands; ++i) {
// A magic function that updates arrayOfCommands with
// the current command goes here. It doesn't make
// sense in the context of the code, so just believe me! :)
// Ex: The contents will be "ls -l" or "wc" depending on
// which time through the loop we are
pid = fork();
if(pid == 0) {
//if not last command
if(i < commands){
if(dup2(pipefds[j + 1], 1) < 0){
perror("dup2");
exit(EXIT_FAILURE);
}
}
//if not first command&& j!= 2*pipes
if(j != 0 ){
if(dup2(pipefds[j-2], 0) < 0){
perror("dup2");
exit(EXIT_FAILURE);
}
}
for(i = 0; i < 2*pipes; i++){
close(pipefds[i]);
}
// Should any of the below inputs be *arrayOfCommands or
// **arrayOfCommands or &arrayOfCommands?
// I'm REALLY bad with pointers
if( execvp(arrayOfCommands, arrayOfCommands) < 0 ){
perror(arrayOfCommands);
exit(EXIT_FAILURE);
}
}
else if(pid < 0){
perror("error");
exit(EXIT_FAILURE);
}
j+=2;
}
for(i = 0; i < 2 * pipes; i++){
close(pipefds[i]);
}
for(i = 0; i < pipes + 1; i++){
}
wait(&status);
}
When I run this, I get a couple of errors:
dup2: Bad file descriptor
ls: |: No such file or directory
ls: wc: No such file or directory
Could someone help me figure out the following two things:
Why am I getting these errors?
In the execvp function, what kinds if pointers am I looking for? arrayOfCommands was initialized as a char *arrayOfArgs[]
First thing:
//if not last command
if(i < commands)
should be
if(i < commands -1)
since igoes from 0 to commands -1
that should solve dup2: Bad file descriptor
ls: |: No such file or directory
ls: wc: No such file or directory
are caused by malformed arrayOfCommands. It has to be initialized by
char * arrayOfCommands[] = {"ls", "-l", NULL};
and
char * arrayOfCommands[] = {"wc", NULL};
respectively and invoked via execvp(arrayOfCommands[0], arrayOfCommands)
essentially arrayOfCommands has to be of the same format that your argument vector (typically argv) of an int main(int argc, char** argv).
I have an assignment to create a Linux shell in C. Currently, I am stuck on implementing redirections and pipes. The code that I have so far is below. The main() parses user's input. If the command is built in, then that command is executed. Otherwise, the tokenized input is passed to execute() (I know that I should probably pull the built-in commands into their own function).
What execute() does is loop through the array. If it encounters <, >, or | it should take appropriate action. The first thing I am trying to get to work correctly is piping. I am definitely doing something wrong, though, because I cannot get it to work for even one pipe. For example, a sample input/output:
/home/ad/Documents> ls -l | grep sh
|: sh: No such file or directory
|
My idea was to get each of the directions and piping work for just one case, and then by making the function recursive I could hopefully use multiple redirections/pipes in the same command line. For example, I could do program1 < input1.txt > output1.txt or ls -l | grep sh > output2.txt.
I was hoping that someone can point out my errors in trying to pipe and perhaps offer some pointers in how to approach the case where multiple redirections/pipes are inputted by the user.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int MAX_PATH_LENGTH = 1024; //Maximum path length to display.
int BUF_LENGTH = 1024; // Length of buffer to store user input
char * delims = " \n"; // Delimiters for tokenizing user input.
const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
void execute(char **argArray){
char **pA = argArray;
int i = 0;
while(*pA != NULL) {
if(strcmp(argArray[i],"<") == 0) {
printf("<\n");
}
else if(strcmp(argArray[i],">") == 0) {
printf(">\n");
}
else if(strcmp(argArray[i],"|") == 0) {
int fds[2];
pipe(fds);
pid_t pid;
if((pid = fork()) == 0) {
dup2(fds[PIPE_WRITE], 1);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
char** argList;
memcpy(argList, argArray, i);
execvp(argArray[0], argArray);
}
if((pid = fork()) == 0) {
dup2(fds[PIPE_READ], 0);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
execvp(argArray[i+1], pA);
}
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
wait(NULL);
wait(NULL);
printf("|\n");
}
else {
if(pid == 0){
execvp(argArray[0], argArray);
printf("Command not found.\n");
}
else
wait(NULL);*/
}
*pA++;
i++;
}
}
int main () {
char path[MAX_PATH_LENGTH];
char buf[BUF_LENGTH];
char* strArray[BUF_LENGTH];
/**
* "Welcome" message. When mash is executed, the current working directory
* is displayed followed by >. For example, if user is in /usr/lib/, then
* mash will display :
* /usr/lib/>
**/
getcwd(path, MAX_PATH_LENGTH);
printf("%s> ", path);
fflush(stdout);
/**
* Loop infinitely while waiting for input from user.
* Parse input and display "welcome" message again.
**/
while(1) {
fgets(buf, BUF_LENGTH, stdin);
char *tokenPtr = NULL;
int i = 0;
tokenPtr = strtok(buf, delims);
if(strcmp(tokenPtr, "exit") == 0){
exit(0);
}
else if(strcmp(tokenPtr, "cd") == 0){
tokenPtr = strtok(NULL, delims);
if(chdir(tokenPtr) != 0){
printf("Path not found.\n");
}
getcwd(path, MAX_PATH_LENGTH);
}
else if(strcmp(tokenPtr, "pwd") == 0){
printf("%s\n", path);
}
else {
while(tokenPtr != NULL) {
strArray[i++] = tokenPtr;
tokenPtr = strtok(NULL, delims);
}
execute(strArray);
}
bzero(strArray, sizeof(strArray)); // clears array
printf("%s> ", path);
fflush(stdout);
}
}
Part of the problem is in the pipe handling code - as you suspected.
else if (strcmp(argArray[i], "|") == 0) {
int fds[2];
pipe(fds);
pid_t pid;
if ((pid = fork()) == 0) {
dup2(fds[PIPE_WRITE], 1);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
char** argList;
memcpy(argList, argArray, i);
execvp(argArray[0], argArray);
}
if ((pid = fork()) == 0) {
dup2(fds[PIPE_READ], 0);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
execvp(argArray[i+1], pA);
}
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
wait(NULL);
wait(NULL);
printf("|\n");
}
The first execvp() was probably intended to use argList since you've just copied some material there. However, you've copied i bytes, not i character pointers, and you've not ensured that the pipe is zapped and replaced with a null pointer.
memcpy(argList, argArray, i * sizeof(char *));
argList[i] = 0;
execvp(argList[0], argList);
Note that this has not verified that there is no buffer overflow on argList; Note that there is no space allocated for argList; if you use it, you should allocate the memory before doing the memcpy().
Alternatively, and more simply, you can do without the copy. Since you're in a child process, you can simply zap replace argArray[i] with a null pointer without affecting either the parent or the other child process:
argArray[i] = 0;
execvp(argArray[0], argArray);
You might also note that the second invocation of execvp() uses a variable pA which cannot be seen; it is almost certainly incorrectly initialized. As a moderately good rule of thumb, you should write:
execvp(array[n], &array[n]);
The invocations above don't conform to this schema, but if you follow it, you won't go far wrong.
You should also have basic error reporting and a exit(1) (or possibly _exit(1) or _Exit(1)) after each execvp() so that the child does not continue if it fails to execute. There is no successful return from execvp(), but execvp() most certainly can return.
Finally for now, these calls to execvp() should presumably be where you make your recursive call. You need to deal with pipes before trying to deal with other I/O redirection. Note that in a standard shell, you can do:
> output < input command -opts arg1 arg2
This is aconventional usage, but is actually permitted.
One good thing - you have ensured that the original file descriptors from pipe() are closed in all three processes (parent and both children). This is a common mistake which you have avoided making; well done.