Making a simple -type shell, using fork and execvp functions to run the commands from the stdin line.
However, things like ls work, but not ls -all -S.
It will execute ls, but nothing will be printed for ls -all
The only idea I can come up with is that there is a "\n" somewhere in the command, but I don't know how to get it out or even where it is....
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
//Libs ^^^^ Defs vvvvvvvv
#define comlen 4096 //Max command length
#define comarg 32 //Max argument length
int main(int argc, char *argv[])
{
char buff; //command buffer
char* comand[comlen];
int i;
do
{
i = 0;
printf("simsh: ");
char* whtspc = strtok (fgets(&buff, comlen, stdin)," "); //get input and tokenize
printf("[%lu] :: %s------------\nEND OF BUFF TEST\n", strlen(&buff), &buff);
while (whtspc != NULL)
{
comand[i]=(char*)malloc((sizeof(char)*strlen(whtspc))); //alloctie mem for commands
strncpy(comand[i], whtspc, strlen(whtspc)-1); //coppy comand token to array index i
whtspc = strtok (NULL, " "); //grab next token
i++; //incriment
/*trying to change new line character to NULL so that commands can be passed properly*/
// if (comand[strlen(comand[i]) - 1] == "\n")
// {
// comand[strlen(comand[i]) - 1] = '\0';
// }
//breka out incase index oversteps
if (i == 4096)
break;
}
//last entry in command should be null
comand[i] = NULL;
//fork to run in background
pid_t pid = fork();
if (pid == 0)
{
//testing and pass comands to execvp
printf("START OF COMAND TEST\n!!!!!!!!!%s!!!!!!!!!!!!!!!!\n %lu\nEND OF COMAND TEST\n\n",comand[1], strlen(comand[0]));
execvp(comand[0], &comand);
}
else
{
//parent wait on child.
waitpid(pid, &i, WUNTRACED | WCONTINUED);
}
}
while(1);
return 0;
}
Any help would be welcomed.
If it helps at all , here is the terminal output of the code::
simsh: ls
[3] :: ls
------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!(null)!!!!!!!!!!!!!!!!
2
END OF COMAND TEST
chop_line.c chop_line.h list.c list.h Makefile Makefile~ One simsh1 simsh1.c simsh1.c~
simsh: ls -all
[2] :: ls------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!-all!!!!!!!!!!!!!!!!
1
END OF COMAND TEST
simsh: echo
[5] :: echo
------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!(null)!!!!!!!!!!!!!!!!
4
END OF COMAND TEST
simsh: echo all
[4] :: echo------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!all!!!!!!!!!!!!!!!!
3
END OF COMAND TEST
simsh: echo echo
[4] :: echo------------
END OF BUFF TEST
START OF COMAND TEST
!!!!!!!!!echo!!!!!!!!!!!!!!!!
3
END OF COMAND TEST
The first argument to fgets should be a pointer to a buffer where the string is copied into. You are passing a pointer to a single char.
Second, execvp expects two arguments: a filename and a null-terminated list of command-line arguments which, by convention, starts with the filename itself.
I took the liberty to make some modifications to your code, both fixing the issues I pointed above and making it a little more readable.
Note that there's a memory leak in the code below (fix it :). There might be other issues that I didn't notice.
I implemented a shell a while ago; if you want to take a look, my GitHub URL is in my profile (BEWARE: ugly college homework code).
Hope it helps!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#define COMLEN 4096
#define COMARG_N 32
#define TRUE 1
int main(int argc, char *argv[])
{
char *token;
char *args[COMARG_N];
char *buff;
int i;
pid_t pid;
while(TRUE) {
printf("simsh: ");
buff = (char*) malloc(sizeof(char) * COMLEN);
fgets(buff, COMLEN, stdin);
if (buff[strlen(buff) - 1] == '\n')
buff[strlen(buff) - 1] = '\0';
i = 0;
token = strtok (buff, " ");
while (token != NULL && i < COMARG_N - 1) {
args[i] = token;
token = strtok (NULL, " ");
i++;
}
args[i] = NULL;
pid = fork();
if (pid == 0)
execvp(args[0], &args[0]);
else
waitpid(pid, &i, WUNTRACED | WCONTINUED);
free(buff);
}
return 0;
}
First of all, your fgets is reading to a single character buff. You should read into a buffer of characters. Second, fgets keeps the newline at the end of the read string, so you may want to remove it first, e.g.:
char buff[4096];
if (!fgets(buff, sizeof(buff), stdin)) {
// error or EOF
return 1;
}
int len = strlen(buff);
if (len > 0 && buff[len-1] == '\n') {
buff[--len] = '\0';
}
char *whtspc = strtok(buff, " ");
You must also replace all references to &buff with buff.
In addition to this, your malloc is also wrong, and allocates one character less than is required (strlen is without the terminating NUL):
if (!(comand[i] = malloc(strlen(whtspc)+1))) {
return 1; // out of memory
}
(void) strcpy(comand[i], whtspc);
Correspondingly your strncpy was copying one character less than required. This is what made your original code accidentally work for a single-word input because it had the effect of removing the trailing '\n' for you in that case, but in every other case it removed the last character of the word itself.
And the second argument to execvp should be just the comand (sic) array:
execvp(comand[0], comand);
Related
I'm trying to build a version of a linux shell wherein, I take in the commands from the user and execute the commands using execv() system call.
I'm using strtok_r to tokenize the commands and pass the arguments to execv(). For example, when the user types in the command "ls -l", I first remove the leading/trailing white spaces and then tokenize the command into two tokens 1) ls, 2) -l. I concatenate the first token with a char path[10] in which I've already copied "/bin/" path. So, after concatenation, path becomes "/bin/ls". However, the execv() call fails. But, when I directly pass "/bin/ls" (instead of passing it as a variable) as the argument in execv(), it works. Code is shown below:
Please let me know where I've committed the mistake.
`
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
void removeTrailingLeadingSpaces(char* stringInput);
int main ()
{
printf("dash> ");
char* input;
size_t characters;
size_t bufsize = 32;
input = (char *)malloc(bufsize*sizeof(char));
characters = getline(&input,&bufsize,stdin);
removeTrailingLeadingSpaces(input);
char *args[2];
char* last;
char* token;
token = strtok_r(input," ",&last);
printf("%s\n", token);
removeTrailingLeadingSpaces(token);
char path[10];
strcpy(path,"/bin/");
strcat(path,token);
args[0] = path;
printf("%s\n",args[0]);
args[1] = strtok_r(NULL," ",&last);
args[2] = NULL;
int i = access(args[0],X_OK);
printf("result is %d",i);
int j= execv(args[0],args);
printf("result2 is %d",j);
return 0;
}
void removeTrailingLeadingSpaces(char* input)
{
while(isspace((unsigned char)*input))
input++;
int i=strlen(input)-1;
while(i>0)
{
if(input[i] == ' '|| input[i] == '\n'|| input[i] =='\t')
i--;
else
break;
}
input[i+1] = '\0';
}
`
I have been trying to get strcmp to return true in the following program for many days now, I have read the man pages for strcmp, read, write... I have other's posts who have had the exact same problem. The following source code is just a test program that is frustrating the heck out of me, there are some commented out lines that are other attempts I've made at getting strcmp to work as expected. I have compiled with 'gdb -g' and stepped through one instruction at a time. The printf statements pretty much tell the whole story. I cannot get the value of buf, or bufptr to equal 't' ever. I have simplified the program, and had it just print one character at a time one after the other to the screen and they print as expected from whatever file is read-in, however, as soon as I start playing with strcmp, things get crazy. I cannot for the life of me figure out a way to get the value in buf to be the single char that I am expecting it to be.
When simplified to just the write(1,...) call, it writes the expected single char to stdout, but strcmp to a single 't' never returns 0. !!!!! Thank you in advance. I originally didnt have bufptr in there and was doing a strcmp to buf itself and also tried using bufptr[0] = buf[0] and the still were not the same.
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define BUF_SIZE 1
void main(int argc, char *argv[])
{
char buf[BUF_SIZE];
int inputFd = open(argv[1], O_RDONLY);
char tee[] = "t";
int fff = 999;
char bufptr[BUF_SIZE];
// char *bufptr[BUF_SIZE];
while (read(inputFd, buf, BUF_SIZE) > 0) {
bufptr[0] = buf[0];
// bufptr = buf;
printf("********STRCMP RETURNED->%d\n", fff); // for debugging purposes
printf("--------tee is -> %s\n", tee); // for debugging purposes
printf("++++++++buf is -> %s\n", buf); // " " "
printf("########bufptr is -> %s", bufptr); // " " "
write (1, buf, BUF_SIZE);
if ((fff = strcmp(tee, bufptr)) == 0)
printf("THIS CHARACTER IS A T");
}
close(inputFd);
}
The str-family of functions expects strings as inputs, which are arrays storing null-terminated character sequences. However, you do not provide space in the buffer for the null character. To make the buffers strings, you need to add space for the null-character and zero-out the value so that they end with the null character.
void main(int argc, char *argv[])
{
char buf[ BUF_SIZE + 1 ] = {0};
int inputFd = open(argv[1], O_RDONLY);
char tee[] = "t";
while (read(inputFd, buf, BUF_SIZE) > 0) {
if ( strcmp( tee, buf ) == 0 )
printf("THIS CHARACTER IS A T");
}
close(inputFd);
}
Here is the general problem:
The program must fork() and wait() for the child to finish.
The child will exec() another program whose name is INPUT by the user.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) {
int status;
char input[BUFSIZ];
printf(">");
scanf("%s",input);
char *args[] = {"./lab1"};
pid_t pid = fork();
if(pid==0){
execvp(args[0],args);
}else if(pid<0){
perror("Fork fail");
}else{
wait(&status);
printf("My Child Information is: %d\n", pid);
}
return 0;
}
My problem is getting the user to input a program name to run (at the ">" prompt) and getting that input into execvp (or another exec() function if anyone has any ideas)
I'm going to hold off lambasting you for using scanf("%s") for now, though you should be aware it's really not robust code.
Your basic task here is going to be taking a character array entered by the user and somehow turning that into an array of character pointers suitable for passing to execvp.
You can use strtok to tokenise the input string into tokens separated by spaces, and malloc/realloc to ensure you have enough elements in an array to store the strings.
Alternatively, since you already have a potential buffer overflow issue, it may be good enough to just use a fixed size array.
For example, the following program shows one way of doing this, it uses a fixed string echo my hovercraft is full of eels and tokenises it to be suitable for execution:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static char *myStrDup (char *str) {
char *other = malloc (strlen (str) + 1);
if (other != NULL)
strcpy (other, str);
return other;
}
int main (void) {
char inBuf[] = "echo my hovercraft is full of eels";
char *argv[100];
int argc = 0;
char *str = strtok (inBuf, " ");
while (str != NULL) {
argv[argc++] = myStrDup (str);
str = strtok (NULL, " ");
}
argv[argc] = NULL;
for (int i = 0; i < argc; i++)
printf ("Arg #%d = '%s'\n", i, argv[i]);
putchar ('\n');
execvp (argv[0], argv);
return 0;
}
Then it outputs the tokenised arguments and executes it:
Arg #0 = 'echo'
Arg #1 = 'my'
Arg #2 = 'hovercraft'
Arg #3 = 'is'
Arg #4 = 'full'
Arg #5 = 'of'
Arg #6 = 'eels'
my hovercraft is full of eels
I wrote this simple shell so far. But I got some trouble with my shell.
For example, when I try to open a pdf file via the command "evince pdffile.pdf", the actual pdfile does not get opened. The pdf viewer runs but the actual file with the whole content never appears.
Or another example is the command "ls -l". I don't get the files and folders listed as it should be, but "ls" is working.
Another example is gedit, and so on.
Also, I should mention. I am not using "system()", because system() would do everything and I would not have something to do. Instead, I am using "execvp()".
Here is the code. I hope, you may find the problem, because I have no clue what the problem is causing.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#define MAX_LENGTH 1024
#define DELIMS " \t\r\n"
void exec_cmd (char *buf);
int main() {
char line[MAX_LENGTH];
char * cmd;
char curDir[100];
while (1) {
getcwd(curDir, 100);
printf("%s#%s$ ", getlogin(), curDir);
if (!fgets(line, MAX_LENGTH, stdin))
break;
if ((cmd = strtok(line, DELIMS))) {
errno = 0;
if (strcmp(cmd, "cd") == 0) {
char *arg = strtok(0, DELIMS);
if (!arg)
fprintf(stderr, "cd: argument is missing.\n");
else chdir(arg);
} else if (strcmp(cmd, "exit") == 0) {
exit(0);
} else exec_cmd(line);
if (errno) perror("Error. Command failure");
}
}
return 0;
}
void exec_cmd (char *buf) {
int status = 0;
char *argv[MAX_LENGTH];
int j=0;
pid_t pid;
argv[j++] = strtok (buf, DELIMS);
while (j<MAX_LENGTH && (argv[j++]=strtok(NULL,DELIMS))!=NULL); // EDIT: " " replaced by DELIMS
pid = fork();
if(pid < 0) {
printf("Error occured");
exit(-1);
} else if(pid == 0) {
execvp(argv[0],argv);
} else if( pid > 0) {
wait(&status);
}
}
The bug is in main().
You are using strtok to find the first word of the line. But strtok modifies the line, making it actually contain just that word (a NUL terminator is written to the string right after it).
You need to make a copy of the line, or use strtok_s, or do something else to avoid modifying the line.
Check the arguments (eg, print them out, each enclosed in []) before you call fork/exec, there's a good chance they're not what you think.
While your first call to strtok uses your full delimiter set, subsequent calls do not. They instead just use a space. That means that the final argument will probably have the newline left on the string by fgets. I'd be using the same delimiter set in the subsequent calls. In other words:
while (j<MAX_LENGTH && (argv[j++]=strtok(NULL,DELIMS))!=NULL);
Having entered your code and done that debugging, I find that the string passed to the function only ever has one word in it. It turns out that's because of the strtok that happened in main to check for cd/exit. That left the nul character at the end of the first word, an effect inherent in the way strtok works.
Probably the quickest fix is to make a copy of the string before the initial strtok in main, then pass that to the function. In other words, use strdup (and, later, free). Now glibc has a strdup but, if you're in an environment that doesn't (it's POSIX rather than ISO), see here.
I am making a simple shell. It also needs to be able to read text files by lines. This is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
// Exit when called, with messages
void my_exit() {
printf("Bye!\n");
exit(0);
}
int main(void) {
setvbuf(stdout, NULL, _IONBF, 0);
// Char array to store the input
char buff[1024];
// For the fork
int fid;
// Get all the environment variables
char dir[50];
getcwd(dir,50);
char *user = getenv("USER");
char *host = getenv("HOST");
// Issue the prompt here.
printf("%s#%s:%s> ", user, host, dir);
// If not EOF, then do stuff!
while (fgets(buff, 1024, stdin) != NULL) {
// Get rid of the new line character at the end
// We will need more of these for special slash cases
int i = strlen(buff) - 1;
if (buff[i] == '\n') {
buff[i] = 0;
}
// If the text says 'exit', then exit
if (!strcmp(buff,"exit")) {
my_exit();
}
// Start forking!
fid = fork();
// If fid == 0, then we have the child!
if (fid == 0) {
// To keep track of the number of arguments in the buff
int nargs = 0;
// This is a messy function we'll have to change. For now,
// it just counts the number of spaces in the buff and adds
// one. So (ls -a -l) = 3. AKA 2 spaces + 1. Really in the
// end, we should be counting the number of chunks in between
// the spaces.
for (int i = 0; buff[i] != '\0'; i++) {
if (buff[i] == ' ') nargs ++;
}
// Allocate the space for an array of pointers to args the
// size of the number of args, plus one for the NULL pointer.
char **args = malloc((sizeof(char*)*(nargs + 2)));
// Set the last element to NULL
args[nargs+1] = NULL;
// Split string into tokens by space
char *temp = strtok (buff," ");
// Copy each token into the array of args
for (int i = 0; temp != NULL; i++) {
args[i] = malloc (strlen(temp) + 1);
strcpy(args[i], temp);
temp = strtok (NULL, " ");
}
// Run the arguments with execvp
if (execvp(args[0], args)) {
my_exit();
}
}
// If fid !=0 then we still have the parent... Need to
// add specific errors.
else {
wait(NULL);
}
// Issue the prompt again.
printf("%s#%s:%s> ", user, host, dir);
}
// If fgets == NULL, then exit!
my_exit();
return 0;
}
When I run it alone as a shell, it works great. When I run ./myshell < commands.txt, it does not work.
commands.txt is:
ls -l -a
pwd
ls
But the output is:
>Bye!
>Bye!
>Bye!
>Bye!
>Bye!
>Bye!>Bye!
>Bye!
>Bye!
>Bye!
Doesn't even run my commands. Any ideas? I thought my while loop was pretty simple.
I don't know if this is the problem, but you (correctly) mention in a comment that you have to allocate "plus one for the NULL pointer" in the *args array.
However, you don't actually set the last pointer in *args to NULL.
execvp() won't like that.
That doesn't explain why there might be a difference between redirected vs. non-redirected input, other than undefined behavior is a bastard.
Sorry everyone - turns out my text file was in some sort of demented format from Mac's TextEdit GUI. Everything is working great.
I really appreciate all of the helpful responses