C - A simple shell on linux - Some trouble with commands - c

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.

Related

Do something when a char variable has a string "cd" in C (Newbie question)

I am very new to C and I'm writing something that will eventually be a shell or at least something like that. I have a char variable command and I have an if-statement that executes (or at least should) function cd()when char command has "cd" as a string. But it doesn't work for some reason and always executes cd.
Sorry if the explenation is trash but english is not my native language and I don't speak it well. Anyways, here is the code (please ignore that chdir doesnt do anything for now):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int loop=1;
char command[512];
while (loop == 1) {
FILE *command_file;
command_file = fopen("/tmp/EXECUTE", "w");
printf("SHELL > ");
fgets(command, sizeof(command), stdin);
fprintf(command_file, "%s", command);
if (strcpy(command, "cd")) {
cd();
}
else {
printf("%s", command);
}
fclose(command_file);
execution();
}
}
void execution() {
char r;
FILE *exec_file;
exec_file = fopen("/tmp/EXECUTE", "r");
system("sh /tmp/EXECUTE");
}
void cd() {
char path;
printf("Enter a path: ");
path = getc(stdin);
chdir();
}
Sorry if the code is a bit cringy but I really need help! If you want just the if statement:
if (strcpy(command, "cd")) {
cd();
}
else {
printf("%s", command);
}
There are two problems with your code:
The fgets reads and includes the newline in the string. So if the input is cd followed by a newline then fgets will put "cd\n" in the buffer.
A Simple way to remove the newline is with the strcspn function:
command[strcspn(command, "\n")] = 0;
The second problem is that strcmp returns zero (i.e. false) when the two strings matches. So your comparison should be:
if (strcmp(command, "cd") == 0)
Oh, and as mentioned in a comment, you should use strcmp to compare strings, not strcpy which is string copy.

Shell in c using strtok and an array of pointers

For this Shell program i'm using the functions strtok (see fragmenta.h code) to parsing a string which is introduced by user.
I need to remove the blanks with strotk function and introduce those on a struct of an array of pointers. This are made in fragmenta.h
In the main program (shell.c), is necessary to introduce the string, this one is passed to fragmenta and stored on char **arg. After that, i use the execvp function to execute the command.
The problem is that the program store the whole command, but only execute the first individual command. For example, if we introduce "ls -al", only execute the ls command so i understand that is a problem on the pointer.
Main program shell.c
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include "fragmenta.h"
//
char cadena[50];
int pid;
int i, status;
char **arg;
pid_t pid;
//
main()
{
printf("minishell -> ");
printf("Introduce the command \n");
scanf("%[^\n]", cadena);
if (strcmp(cadena, "exit") == 0)
{
exit(0);
}
else
{
pid = fork();
if (pid == -1)
{
printf("Error in fork()\n");
exit(1);
}
else if (pid == 0) //child proccess
{
arg = fragmenta(cadena);
if (execvp(*arg, arg) < 0) /* execute the command */
{
printf("*** ERROR: exec failed\n");
exit(1);
}
}
else /* for the parent: */
{
while (wait(&status) != pid);
}
}
}
int len;
char *dest;
char *ptr;
char *aux;
char **fragmenta(const char *cadena)
{
//
char *token;
int i = 0;
//
len = strlen(cadena);
char *cadstr[len + 1];
dest = (char *)malloc((len + 1) * sizeof(char));
strcpy(dest, cadena);
//printf("Has introducido:%s\n",dest);
token = strtok(dest, " ");
while ( token != NULL)
{
cadstr[i] = malloc(strlen(token) + 1);
strcpy(cadstr[i], token);
token = strtok(NULL, " ");
i++;
}
*cadstr[i] = '\0';
ptr = *cadstr;
i = 0;
while (cadstr[i] != NULL)
{
//printf("almacenado: %s\n",cadstr[i]);
i++;
}
return &ptr;
}
You've got at least two problems.
The first one is this:
ptr=*cadstr;
You've gone through all that trouble to create an array of arguments, and then you just copy the first argument and return a pointer to that copy.
You could just get rid of ptr and return cadstr, except that it's a local variable, so as soon as the function returns, it can be overwritten or deallocated.
Since you're storing everything else in the universe as globals for some reason, the obvious fix to that is to make cadstr global too. (Of course you can't use a C99 runtime-length array that way, but since you've written your code to guarantee a buffer overrun if the input is more than 50 characters, you can safely just allocate it to 50 strings.)
A better solution would be to initialize a new array on the heap and copy all of cadstr into it. Or just initialize cadstr on the heap in the first place.
Second, you never append a NULL to the end of cadstr. Instead, you do this:
*cadstr[i] = '\0';
That leaves the last element in cadstr pointing to whatever uninitialized pointer it was pointing to, but modifies the first byte of whatever that is to be a 0. That could corrupt important memory, or cause a segfault, or be totally harmless, but the one thing it can't do is set cadstr[i] to point to NULL.
When you check this:
i = 0;
while (cadstr[i] != NULL)
i++;
… you only get out of that loop because of luck; you read right past the end of the allocated array and keep going until some other structure or some uninitialized memory happens to be sizeof(void*) 0s in a row.
And when you pass the same thing to execvp, who knows what it's going to do.
You're also declaring main without a prototype, which is deprecated, so you'll probably get a warning for it from any compiler that accepted the rest of your code. To fix that, just do int main().

Implementing a simple shell

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);

fgets loops many times before exiting for EOF

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

Character arrays, and pointers how to use strtok and strcmp

I'm writing a linux shell for my operating systems class. I've knocked out the majority of it but I'm stuck on simple string comparisons. I've everything I can think of. strcmp should take in \0 terminated strings and return 0 for equal but that doesn't seem to be working and even stepping through the array and checking each char isn't working either. I currently have cmd[0] in the strcmp I know thats not right it needs to be null terminated but I've tried using strcpy and strcat \0 to another string. If someone could point out my mistake it would be much appreciated.
//Matthew Spiers
//CSC306
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;
void ckCmd(char dir[]);
int main(){
pid_t pid;
char cdstr[4] = "cd";
char str[50];
char *cmd[3];
char *pstr;
char temp[50];
char dir[50] = "/bin/";
while(1){
pid = fork();
if(pid < 0){
fprintf(stdout, "Fork Failed");
}
else if(pid == 0){
fprintf(stdout, "\e[36m306.SH>\e[0m");
fgets(str, 50, stdin);
for(int i =0; i<50; i++){
if(str[i] == '\n'){
str[i] = '\0';
}
}
strcpy(temp, str); // Make a copy of original string
cmd[0] = strtok(str, " ");
for(int i =1; i<3; i++){
cmd[i] = strtok(NULL, " ");
}
strcat(dir, cmd[0]);
cout << cmd[0];
pstr = strtok(temp, " "); //pull out only first token
//Change Directory
if(!strcmp(pstr, "cd")){ //if first token is cd
//either branch to a routine just change directory
//ie branch and change directory
}
//ckCmd(temp);
execlp(dir, cmd[0], cmd[1], cmd[2], NULL);
_exit(0);
}
else{
wait(NULL);
}
}
}
void ckCmd(char str[]){
char *p;
p = strtok(str, " ");
if(p[0] == 'c'){
chdir("new");
}
}
enter code here
strtok is not reentrant/thread-safe!
You should use the RETURN-value from strtok:
p = strtok(str, " ");
if(p[0] == 'c'){
cmd[0] = strtok(str, " ");
...
if(!strcmp(cmd[0], "cd")){
If p/cmd[0] is NULL, it will crash.
What exactly isn't working? Can you show a smaller code sample?
The line:
strcat(dir, cmd[0]);
Are you aware that dir is the target here and not cmd[0]?
The line: !strcmp(cmd[0], "cd") by itself is correct, but it's unclear what exactly you're trying to do. Could you comment each group of line with your intentions?
Update: I suggest that you're trying too many things at once. To home in on your problem I recommend the following steps:
Understand how strtok works. It isn't very hard - read its manual and then try it in a separate file with some strings. This should give you a good idea.
Break parts of your code out and provide them with pre-set input (not from the user and without the forking). See where the behavior is as expected and where it drifts off.

Resources