So I am reading commands from a file that has a line of commands, each separated by a delimiter which is a semi colon. I got these commands into an array and I am basically executing them one by one. Everything works fine until I have a command that has an option and execvp fails and I don't know how to fix this.
Here is my code:
int main(int argc, char *argv[])
{
char delim[] = ";"; // the semicolon is the commands separator
FILE* batchFile;
char oneLine[512];
batchFile = fopen("myfile.txt", "r");
int numOfCommands = 0;
char *commands[100];
char *oneCommand;
pid_t childPid;
int child_status;
if(batchFile == NULL)
{
perror("Error opening file ... exiting !");
exit(1);
}
if(fgets(oneLine,512,batchFile) != NULL)
{
//puts(mystring);
fclose(batchFile);
}
printf("The command is: %s \n", oneLine);
oneCommand = strtok(oneLine,delim);
commands[numOfCommands++] = strdup(oneCommand);
while((oneCommand=strtok(NULL, delim))!=NULL)
{
commands[numOfCommands++] = strdup(oneCommand);
}
commands[numOfCommands] = NULL;
for(int i = 0;i < numOfCommands;i++)
{
printf("The command is: %s \n",commands[i]);
}
for(int i =0;i < numOfCommands;i++)
{
childPid = fork();
if(childPid == 0)
{
execvp(commands[i], argv);
perror("exec failure");
exit(1);
}
else
{
wait(&child_status);
}
}
return 1;
}
and some commands like exit, cd will not work, I guess maybe because they are not in /bin ??
and how could this be fixed ?
My file has the following line
ls;date;cal;pwd;cd;ls -l;
and when I run my program it outputs the following.
If you look at the output, it fails for cd. This is expected because cd is a shell built-in, not a command. For cd, you have to use chdir(2) instead of execv().
ls -l fails because there's no such command. You need to split the command again before passing them to execvp().
Basically, the command you pass has to be in the form:
char *cmd[] = {"ls", "-l", 0};
execvp(cmd[0], cmd);
Related
I'm trying to execute the command by execv() in child process (using fork()) and test the result by simple 'ls' command in linux.
However, execv does not return error(-1) but not printing anything when I input 'ls' command. I can't understand what is wrong. There are few files in directory with the c file it self containing the code so there should be some output for 'ls'
`
if ((pid = fork()) == 0) {
command(argv);
if (execv(argv[0], argv) < 0) {
fprintf(stderr,"%s: Command not found.\n", argv[0]);
exit(0);
}
exit(0);
}
argc = 0;
char* ptr = strtok(buf, " ");
while (ptr!=NULL) {
argv[argc++] = ptr;
ptr = strtok(NULL, " ");
}
argv[argc] = NULL;
if (!strcmp(argv[0],"ls") || !strcmp(argv[0], "man") ||
!strcmp(argv[0], "grep")
|| !strcmp(argv[0], "sort") || !strcmp(argv[0], "awk") ||!strcmp(argv[0], "bc")) {
strcat("/bin/", argv[0]);
`
I tried changing strcat for adding /bin/ to sprintf, strcpy, but I don't think it is problem for argv[0] because execv understand the command.
may be it is error with argv but there is only /bin/ls in it. What can go wrong?
execv() system call (execv() man page) has the signature:
int execv(const char *path, char *const argv[]);
so it needs two arguments: 1) the path of the command to execute, 2) the arguments of the command to execute.
The issues in your code are:
argv[0]. You're using argv[0] but, instead you may want to use argv[1], since argv[0] is the name of your executable file, while argv[1] is the first argument from the command line. Thus, the path of the command to pass to execv() is argv[1].
The argument passed to execv() since you're passing all the command line parametes that are different to the parametes you need to send to the execv() function.
To fix your code use these lines:
char* arg[] = {"ls", "-l", NULL};
if ((pid = fork()) == 0) {
if (execv(argv[1], arg) < 0) {
...
}
}
I am creating a Linux type shell program for a school project. So far I have implemented the basic Linux external commands like "ls","ps", etc., using execvp and basic pipes. As part of the project, the user can either run the program in interactive mode or batch mode. In interactive mode the user just enters a command when prompted. For batch mode, the user specifies a file in the command line where there is a list of commands to execute.
The problem I am having is in batch mode. In batch mode, if an invalid command is listed (e.g. "kdfg", for which "kdfg: command not found" while be the output), everything afterward continues, but everything afterward is executed twice. so if I have a "kghd" on one line and the next line is "ls", then the "ls" command will be executed twice. I've literally been looking at my code for hours and have tried a bunch of stuff, but to no avail.
My code is displayed below:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include<sys/wait.h>
#include<unistd.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
char* InputString();
char** GetArgs(char* com);
void Free(char** args);
char** GetCommands(char** line);
void PipedCommands(char* line);
int batch = 0; //Acts as bool for if there is a batch file given at command line
FILE* bFile; //This is just to make a quick tweek to the file if necssary to prevent any undefined behavior and keep track of where we are in the fil.
int b_fd;
int stdin_cpy;
int main(int argc, char** argv)
{
pid_t pid;
int status;
int fd;
int exitCom = 0; //acts as bool to check if an exit command was given.
char* line;
if(argc > 1) //check if batch file was given.
{
/*
if(freopen(argv[1], "r", stdin) == NULL) //I added this in case the file isn't found
{
printf("\nCould not open \"%s\". Terminated\n\n", argv[1]);
exit(1);
}
*/
//The following is to append a newline at the end of the file (if there isn't one).
//For some reaosn, there seems to be some undefined behavior if the input file isn't
//stricitly ended with a newline.
bFile = fopen(argv[1], "r+"); //open for reading and updating
if(bFile == NULL)
{
printf("\nCould not open \"%s\". Terminated\n\n", argv[1]);
exit(1);
}
fseek(bFile, -1, SEEK_END); //go to last character of file
if(fgetc(bFile) != '\n') //If last character is not a newline, append a newline to the file.
{
fprintf(bFile, "\n");
}
fclose(bFile); //close the file.
bFile = fopen(argv[1], "r"); //open file to keep track of when it ends
b_fd = open(argv[1], O_RDONLY); //open file again (with file descriptor this time) to duplicate it to stdin
stdin_cpy = dup(fileno(stdin)); //keep track of stdin file.
dup2(b_fd, 0); //duplicate to stdin so program takes input from bFile
close(b_fd);
batch = 1;
}
//int i=0; //this was used for debugging purposes
while(1)
{
printf("\n");
char** coms = GetCommands(&line);
for(int i=0; coms[i] != NULL; ++i) //loop goes through each command returned from GetCommands(...)
{
//fork and wait.
pid = fork();
wait(&status);
if(pid == 0)
{
int pipedCommand = 0;
//printf("\ncoms[%d]: %s\n", i, coms[i]);
for(int j=0; j<strlen(coms[i]); ++j)
{
if(coms[i][j] == '|')
{
pipedCommand = 1;
break;
}
}
if(pipedCommand == 1)
{
PipedCommands(coms[i]);
exit(1);
}
char** args = GetArgs(coms[i]);
//printf("\nargs[0]: %s\n", args[0]);
if(strcmp(args[0],"exit") == 0)
{
exit(5); //if exit command was given, exit status will be 5 (I just used 5 becuse I felt like it).
}
//printf("\nNo exit\n");
printf("\n");
execvp(args[0],args);
printf("%s: command not found\n", args[0]);
exit(1); //Normal exit exits with 1 or 0.
}
//Parent continues after child exits
else if(pid > 0)
{
//check exit status of child
if(WEXITSTATUS(status) == 5)
exitCom = 1; //set bool exitCom to 1 (true), indicating that the exit command was given
}
}
if(pid > 0)
{
free(line);
free(coms);
//Now that all commands in the line were executed, check exitCom and if it is 1 (exit command was given), the shell can now exit.
if(exitCom == 1)
{
printf("\n");
exit(0);
}
}
/*
if(i >= 5)
{
printf("\nFORCED EXIT\n"); //this was used for debugging purposes
exit(1);
}
++i;
*/
}
return 0;
}
char* InputString()
{
int len = 20;
char* str = (char*)malloc(sizeof(char)*len);
char* buff;
unsigned int i=0;
if(str != NULL)
{
int c = EOF;
//printf("%c", fgetc(bFile));
while( ((c = getchar()) != '\n') && (c != EOF) )
{
/*
//printf("%c", fgetc(bFile));
//fgetc(bFile);
if(feof(bFile))
{
printf("\n\nEnd of the line\n\n");
}
*/
str[i++] = (char)c;
if(i == len)
{
len = len*2;
str = (char*)realloc(str,sizeof(char)*len);
}
}
str[i] = '\0';
buff = (char*)malloc(i);
}
if(batch == 1)
{
if(fgets(buff, i, bFile) == NULL) //Once the end of file has been reached
{
dup2(stdin_cpy, 0); //revert input back to original stdin file so user can now enter commands interactively (this happens if exit command was not given)
close(stdin_cpy); //close stdin_copy
fclose(bFile); //close bFile as we have reached the end of it
batch = 0;
}
}
printf("\n");
return str;
}
//User enters a line of commands (1 or more). Commands are separated with a ';' being the delimeter.
char** GetCommands(char** line)
{
char** coms = (char**)malloc(sizeof(char*));
char delim[] = ";";
if(batch == 0)
printf("prompt> ");
fflush(stdout);
*line = InputString();
if(batch == 1)
printf("%s\n", *line);
strcat(*line, ";");
int i=0;
coms[i] = strtok(*line, delim);
while(coms[i] != NULL)
{
++i;
coms = (char**)realloc(coms, sizeof(char*) * (i+1));
coms[i] = strtok(NULL, delim);
//printf("\ni: %d\n", i);
}
return coms;
}
//A command obtained from GetCommands(...) is separated into various arguments with a space, ' ', being the delimiter.
char** GetArgs(char* com)
{
char** args = (char**)malloc(sizeof(char*));
char delim[] = " ";
//printf("\nline: %s\n", line);
int i=0;
args[i] = strtok(com, delim);
while(args[i] != NULL)
{
++i;
args = (char**)realloc(args, sizeof(char*) * (i+1));
args[i] = strtok(NULL, delim);
}
return args;
}
void PipedCommands(char* line)
{
char** coms = (char**)malloc(sizeof(char*));
int numComs;
char delim[] = "|";
int i=0;
coms[i] = strtok(line, delim);
while(coms[i] != NULL)
{
++i;
coms = (char**)realloc(coms, sizeof(char*) * (i+1));
coms[i] = strtok(NULL, delim);
}
numComs = i;
int fd[2];
pid_t pid;
int status;
int prev_p = 0;
// printf("\nnumComs: %d\n", numComs);
for(int i=0; i<numComs; ++i)
{
//printf("\ni: %d\n", i);
pipe(fd);
pid = fork();
wait(&status);
if(pid == 0)
{
//printf("\nChild\n");
if(i < numComs-1)
{
//printf("\ni < numComs-1\n");
//printf("%s", coms[i]);
//printf("coms[%d]: %s", i, coms[i]);
//printf("\nBefore dup2\n");
char** args = GetArgs(coms[i]);
//printf("\nexecvp in if\n");
if(prev_p != 0)
{
dup2(prev_p, 0);
close(prev_p);
}
dup2(fd[1], 1);
close(fd[1]);
execvp(args[0],args);
printf("%s: command not found\n", args[0]);
exit(3);
}
else
{
//printf("\nelse\n");
//printf("coms[%d]: %s", i, coms[i]);
//printf("\nBefore dup2 in else\n");
if(prev_p != 0)
{
dup2(prev_p, 0);
close(prev_p);
}
//close(fd[0]);
close(fd[1]);
char** args = GetArgs(coms[i]);
printf("\n");
execvp(args[0],args);
printf("%s: command not found\n", args[0]);
exit(3);
}
}
close(prev_p);
close(fd[1]);
prev_p = fd[0];
if(WEXITSTATUS(status) == 3)
{
close(fd[0]);
close(prev_p);
close(fd[1]);
return;
}
}
close(fd[0]);
close(prev_p);
close(fd[1]);
}
You can probably ignore the PipedCommands(...) function as I do not think the problem lies there.
Below is a simple batch file:
kldfg
whoami
Below is the output using the above batch file
kldfg
kldfg: command not found
whoami
jco0100
whoami
jco0100
The whoami command should only execute once, but it appears to execute twice. After that, the program reverts to interactive mode as it should and everything runs fine from there. Does anyone have any idea why this is happening. This only happens when an unknown command is entered. If all commands in the batch file are valid, nothing is outputted twice. It's only for batch files that have an unknown command that all commands after the unknown one are outputted twice.
Here's another example:
Batch file:
date
kldfg
whoami; ls | wc -l
date | wc -c
Output:
date
Tue Apr 13 19:43:19 CDT 2021
kldfg
kldfg: command not found
whoami; ls | wc -l
jco0100
34
date | wc -c
29
whoami; ls | wc -l
jco0100
34
date | wc -c
29
I got it working by disconnecting stdin on the child process before running the command:
...
freopen("/dev/null", "r", stdin); // disconnect
execcvp(args[0], args);
...
From this link: If I fork() and then do an execv(), who owns the console?
I had a crack at debugging it. To get you started on that road:
Compile your C program with debugging symbols:
$ gcc --debug your-program.c
Now debug your C program:
$ gdb a.out
This start a gdb interactive shell.
In gdb itself:
(gdb) list
(gdb) set follow-fork-mode parent
(gdb) breakpoint 69
list your code
tell the debugger to follow the parent when fork()
set a breakpoint at line 69
Run the program:
(gdb) run batch.txt
It will pause at line 69. Execute next line:
(gdb) next
Print a variable:
(gdb) print *coms
Continue running:
(gdb) continue
I leave the rest for you to explore.
I'm still not sure what's wrong with it. Something strange happens in InputString() after your fork fails with an unknown command. InputString() begins returning duplicates from getchar().
I didn't even know you could do that with stdin. Maybe just read from the file in a normal fashion, rather than clobbering stdin, and see if the problem goes away.
Don't write linux code much, I am trying to do more of that.
I took the fork and wait commands out so I could build it in mingw64 (because they arent supported in windows builds) and can't seem to reproduce the issue.
So I think the issue is in the multi-threading setup you have going there.
The "pid" variable is shared between every fork. which means when the fork command is called "pid" is set to whatever the last fork in the loop returned.
It looks like you are using an if statement checking the "pid" variable to see if this thread can execute the command. But wouldn't the main thread keep running right through that?
I don't know what fork() returns but "pid" is uninitialized, don't know if that matters.
Maybe this helps?
I wrote a program that reads a bash file and execute the commands inside that file. I got the execution part working, but I can't seem to be able to redirect the command output to a pipe, then read from the pipe and print its content. I've read some topics on the subject but the solution do not seem to work. Here is the code,
int main(int argc, char * argv[]) {
int pipefd[2];
char buf[1024];
int bytes = 0;
for (int i = 1; i < argc; i++) { // reading file as argument one at a time
FILE * fp;
fp = fopen(argv[i], "r");
char * buffer = NULL;
size_t buf_size = 0;
char bufread[1024];
int bytes_read = 0;
while (getline( & buffer, &buf_size, fp) != EOF) {
pid_t pid;
pid = fork();
pipe(pipefd);
if (pid == 0) {
close(pipefd[0]);
dup2(pipefd[1], 1);
dup2(pipefd[1], 2);
close(pfd[1]);
int length = countWords(buffer);
char * init_argv[length + 2];
init_argv[0] = "sh";
init_argv[1] = "-c";
init_argv[2] = buffer;
init_argv[3] = NULL;
execv("/bin/bash", init_argv);
} else {
int status;
close(pfd[1]);
waitpid(pid, & status, 0);
while (read(pfd[0], bufread, sizeof(bufread)) != 0) {
fprintf(stdout, "%s\n", bufread);
}
}
}
}
}
return 0;
}
Why there isn't any output when this program is run with a valid file containing commands as argument? I know without the dup2 and close instructions, the program execute the commands so that is not the problem. As soon as I add the dup2 and close instruction to the program, I can't debug it anymore (it crash at execv).
I'm not sure if this is the problem, but shouldn't the call pipe(pipefd); be executed before the call to fork?
As it is now, I think the parent and the child each create a different pipe, and they cannot use those pipes to communicate with one another, if that is what you want to do.
Probably the core of this question has been asked a lot on this site.
I'mm working with pocketsphinx and I'm trying to play music each time I request it.
When I say "MUSIC" the program executes the music, my idea is that when I say "STOP" music should stop. I'm trying to get the PID the following way. I got this idea from this question
I though using popen I will get the PID, but isn't that way when it get to pid_t pid = strtoul(line, NULL, 10); it's returning me 0.
How I can get this PID and continue with the program running at the same time?
I'm using the same template that you will find on the pocketsphinx want to see it with modifications here: http://pastebin.com/Duu2nbCA
if(strcmp(word, "MUSIC") == 0)
{
FILE *fpipe;
char *command = (char *)"aplay BobMarley.wav";
char line[256];
if ( !(fpipe = (FILE*)popen(command,"r")) )
{ // If fpipe is NULL
perror("Problems with pipe");
exit(1);
}
fgets( line, sizeof line, fpipe);
pid_t pid = strtoul(line, NULL, 10);
printf("The id is %d\n", pid);
}
You can refer the below code to find the PID of a process. Execute with "root" permission
The argument to the executable will the name of the process for which the PID has to obtained
#define TMP_FILE "/tmp/pid"
int main(int argc, char** argv)
{
FILE *fpipe;
int pid = 0;
char command[50] = "pidof -s ";
if (argc != 2) {
printf("Invalid input\n");
return -1;
}
strcat(command, argv[1]);
strcat(command, " > "TMP_FILE);
system(command);
fpipe = fopen(TMP_FILE, "r");
fscanf(fpipe, "%d", &pid);
printf("The pid is %d\n", pid);
fclose(fpipe);
return 0;
}
Based on the sizeof the process name vary the length of the command.
Implementation 2
int main( int argc, char *argv[] )
{
FILE *fp;
char path[10];
fp = popen("/sbin/pidof -s YOUR_APP", "r");
if (fp == NULL) {
printf("Failed to run command\n" );
exit(1);
}
/* Read the output a line at a time - output it. */
while (fgets(path, sizeof(path), fp) != NULL) {
printf("%s", path);
}
pclose(fp);
return 0;
}
Change YOUR_APP with your application name.
Tested with other commands.
I am very new to C and am in an OS class where I need to write a basic shell in C (yay). It's actually been going halfway decently, I am just trying to learn C basics while getting through the work.
I am trying to use exec after forking and call, for now, mkdir. The arguments required through me off a little, but I've been trying to figure it out and was hoping someone could tell me where I've gone wrong.
} else {
//fork exec
int pid = fork();
if (pid == 0) {
printf("%s",my_argv[0]);
execve("/bin/mkdir",my_argv,0);
} else wait(NULL);
}
This is the portion where I am responding to the mkdir call. Right now, I have a line[] that is input from the user, the command is taken with
command = strtok(line, DELIMITERS);
The arg is :
arg = strtok(0,DELIMITERS);
my_argv[0] = arg;
Everything compiles fine but the mkdir never works. Printing my_argv[0] gives the correct argument that I expect. I'm sure this is something stupid but any tips would be appreciated.
All Code:
int main(int argc, char *argv[])
{
char *command;
char line[MAXLINE];
char *arg = NULL;
char *my_argv[];
while(1) {
printf(PROMPT);
if (fgets(line,MAXLINE,stdin) != NULL) {
//take out \n
line[strlen(line)-1] = '\0';
}
//looks for first delimiter, saves as the command
command = strtok(line, DELIMITERS);
//start looking at what command it is by comparing
if (strcmp(command,"cd")==0) {
//if they equal zero, they match
//this is a cd command, must have following arg
if (argv[1] == NULL) chdir("/");
else chdir(argv[1]);//chdir is the system call for cd
} else if (strcmp(command,"exit")==0) {
break;
} else if (strcmp(command,"mkdir")==0){
arg = strtok(0,DELIMITERS);
my_argv[0] = arg;
my_argv[1] = NULL;
if (!arg) {
printf("Usage: mkdir missing arg\n");
} else {
//fork exec
int pid = fork();
if (pid == 0) {
printf("%s",my_argv[0]);
//mkdir(arg);
execve("/bin/mkdir",my_argv,0);
} else wait(NULL);
}
}
}
return 0;
}
argv[0] contains the name of the program
argv[1] is the first argument
argument list must be NULL terminated
You could use the mkdir syscall instead of execve