Create simple shell in C with a "Set prompt" command - c

#include <string.h>
#include <errno.h>
#define MAX_LENGTH 1024
#define SPACES " \t\r\n"
int main(int argc, char *argv[]){
char line[MAX_LENGTH];
char *cmd;
char *PROMPT = "SUPER SHELL!!";
while(1){
printf(PROMPT);
if (!fgets(line, MAX_LENGTH, stdin)) break;
//parse and execute commands
if((cmd = strtok(line, SPACES))){
// clean errors
errno=0;
if (strcmp(cmd, "quit") ==0){
break;
}
else if(strcmp(line, "Set Prompt") == 0){
char *arg = strtok(0, SPACES);
if (!arg){
fprintf(stderr, "You were missing a prompt. \n");
}
else {PROMPT =arg;
}
}
else system(line);
if(errno) perror ("Command failed. sorry");
}
return 0;
}
I am writing a program in which I have to write my own shell in C.
There must be two commands : quit, which quits the program, and Set Prompt which changes the prompt.
For some reason quit works but Set prompt does not.
How can I fix this?
Thank you

Your program indeed seems to have a lot of problems.
Trying to give you some hints, where to improve without solving the whole exercise for you, here some points I stumbled across:
Gemeral problems
The program doesn't compile!
missing closing brace (most probably on line 35) resulting in:
error: expected declaration or statement at end of input
missing include of stdio.h resulting in:
error: ‘stdin’ undeclared (first use in this function)
...
the last one rather a result of the -Werror switch used in my compilation attempt:
error: format not a string literal and no format arguments [-Werror=format-security]
Nevertheless it might not be the worst idea to change printf(PROMPT); on line 13. (see: http://en.wikipedia.org/wiki/Uncontrolled_format_string)
Logic problems
the block following
else if(strcmp(line, "Set Prompt") == 0){ on line 24
will never be executed as the prior call to strtok() will have replaced any input of the form "Set Prompt whatever..." with "Set\0Prompt whatever..." not(!) with "Set Prompt\0whatever..."
Even after fixing this issue there will still be some spooky behavior left.
To reproduce try changing the set prompt command to "SetPrompt" in your code and start a session. I'd be surprised if you came up with a different result than me:
SUPER SHELL!!SetPrompt myprompt:
myprompt:what the hell is going on here?
sh: 1: what: not found
ell is going on here?
quit
Hint: the simple assignment on line 29 else {PROMPT =arg; will not do, as the memory arg is pointing to will be overwritten the next time fgets(line, MAX_LENGTH, stdin) gets called on line 14.
Try to reserve some extra memory for the prompt string and look up strcpy()

Related

How is the speed of printf affected by a presence of a forked process and '\n'?

I had this simple shell like program that works both in interactive and non-interactive mode. I have simplified the code as much as I can to present my question, but it is still a bit long, so sorry for that!
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
/**
*main-entry point for gbk
*Return: returns the index of 0 on sucess
*/
int main(void)
{
char *cmd = malloc(1 * sizeof(char)), *cmdargs[2];
size_t cmdlen = 0;
int childid, len;
struct stat cmdinfo;
while (1)
{
printf("#cisfun$ ");
len = getline(&cmd, &cmdlen, stdin);
if (len == -1)
{
free(cmd);
exit(-1);
}
/*replace the ending new line with \0*/
cmd[len - 1] = '\0';
cmdargs[0] = cmd;
cmdargs[1] = NULL;
childid = fork();
if (childid == 0)
{
if (stat(*cmdargs, &cmdinfo) == 0 && cmdinfo.st_mode & S_IXUSR)
execve(cmdargs[0], cmdargs, NULL);
else
printf("%s: command not found\n", *cmdargs);
exit(0);
}
else
wait(NULL);
}
free(cmd);
exit(EXIT_SUCCESS);
}
To summarize what this program does, it will first print the prompt #cisfun$ , waits for an input in interactive mode and takes the piped value in non-interactive mode, creates a child process, the child process checks if the string passed is a valid executable binary, and if it is, it executes it other wise it prints a command not found message and prompts again.
I have got this program to work fine for most of the scenarios in interactive mode, but when I run it in non-interactive mode all sorts of crazy (unexpected) things start to happen.
For example, when I run echo "/bin/ls"|./a.out, (a.out is the name of the compiled program)
you would first expect the #cisfun$ message to be printed since that is the first thing performed in the while loop, and then the output of the /bin/ls command, and finally #cisfun$ prompt, but that isn't what actually happens. Here is what happens,
It is very weird the ls command is run even before the first print message. I, at first, thought there was some threading going on and the printf was slower than the child process executing the ls command. But I am not sure if that is true as I am a noob. and also things get a bit crazier if I was printing a message with '\n' at the end rather than just a string. (if I change printf("#cisfun$ "); to printf("#cisfun$\n");) the following happens,
It works as it should, so it got me thinking what is the relation between '\n', fork and speed of printf. Just in short what is the explanation for this.
The second question I have is, why doesn't my program execute the first command and go to an interactive mode, I don't understand why it terminates after printing the second #cisfun$ message. By checking the status code (255) after exit I have realized that the effect is the same as pressing ctr+D in the interactive mode, which I believe is exited by the getline function. But I dont understand why EOF is being inserted in the second prompt.

How do you generally scan/parse commands in interactive REPL programs in C, is there a standard way?

I am writing an interactive REPL program in c.
Some examples of commands (lines starting with >) I would like to handle are:
$ ./my_program // run the program
> add user
id: 123 // this is the output of above command
> update user 123 name "somename"
> remove user 123
> quit
So basically the command is a line with multiple strings.
This is how I am trying to handle the commands.
scan the whole line
parse the command and get a corresponding int value unique to command
do whatever needs to be done for the command
#include <stdio.h>
int parse_cmd(const char *buffer)
{
// parse command
}
int main(int argc, const char **argv)
{
// init code
char buffer[100];
int cmd;
while (1) {
printf("> ");
scanf("%[^\n]%*c", buffer);
cmd = parse_cmd(buffer);
if (cmd < 0) {
printf("error: invalid command\n");
continue;
}
switch (cmd) {
// handle commands
}
}
// deinit code
}
There are a lot of cli programs I have seen that take command inputs in similar way.
I wonder if there is a general way of writing cli programs?
I can write code to parse the commands, just wanted to know the standard approach for such situations?
While there's no real standard way, quite a lot of opensource console tools with an interactive mode use the GNU readline library (https://tiswww.case.edu/php/chet/readline/rltop.html).
It's actually quite easy to use, even simpler than implementing everything 100% correctly by yourself.
Your example rebased on readline:
int main(int argc, const char **argv)
{
// init code
int cmd;
char* line;
while (1) {
line = readline("> ");
if (line) {
cmd = parse_cmd(line);
switch (cmd) {
// handle commands
default:
printf("error: invalid command\n");
}
free(line);
} else {
break;
}
}
// deinit code
}
This isn't any more complex than your example, but you immediately gain:
command line editing at the interactive prompt, with correct handling of each and every possible terminal
correct handling of EOF (important if stdin is redirected)
unlimited input line size
And it's not very hard to add a command history, with arrow-up and down to repeat previous lines, incremental search, optionally persisted to a file, et et.
There's not really a standard way to do it. This is not a 100% fair comparison, but your question is kind of like if there is a standard way to construct a compiler, because you are in fact constructing a language, although a very simple one.
But one reasonably common way that works fairly well for simple programs is this approach. Let's assume that we have two commands add and del. Create a function for both these commands. First we search for one of the strings "add " or "del ". Notice the spaces. Put a pointer on the next character and call the corresponding function with the rest of the line as argument and allow them to determine things.
Here is some pseudo:
parse(bufferptr)
word = getFirstWord(bufferptr)
ptr = bufferptr + strlen(word)
if word == "add"
return add(ptr)
else if word == "del"
return del(ptr)
return -1
add(bufferptr)
word = getFirstWord(bufferptr)
if userExist(word)
return -1
else
return addUser(word)
del(bufferptr)
word = getFirstWord(bufferptr)
if not userExist(word)
return -1
else
return delUser(word)
buffer = input()
int res = parse(buffer)

basic CLI program in C

Okay so overall im trying to complete a basic CLI C program which will complete functions such as clear, quit, cd, ls, help (bring up the unix man) etc.. i altered my code and so far i have this, im getting segmination error when trying to execute the cd command part of the program, (im very new to c btw);
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main (int argc, char *argv[])
{
char input[] = " ";
char *argument;
while(strcmp(input, "quit")!= 0)
{
printf("$");
scanf ("%s", input);
if(strcmp(input,"clear") == 0)
{
printf("\e[1;1H\e[2J");
}
else if(strcmp(argv[1],"cd") == 0)
{
if(chdir(argv[2]) == -1)
{
printf("\n directory does not exists");
}
}
else if(strcmp(input, "echo") == 0)
{
char str[50];
scanf("%[^\n]+", str);
printf(" %s", str);
}
}
}
input is declared as a ' ' (space) character. It will never match 'cd'.
This is probably more along the lines of what you want to achieve, where the first parameter is the command (cd), and the second will be the directory:
int main (int argc, char *argv[])
{
char *argument;
if(strcmp(argv[1],"cd") == 0)
{
if(chdir(argv[2]) == -1)
{
printf("\n directory does not exists");
}
}
Edit Also please note that there is no need for the else satement. If chdir does not return an error, it will change the directory, thus no need to call it again in an else.
Additionally, another tip for using system calls in general, it would be of great help if you print the error number returned by the system upon a failure in system call. This will make things easier when things start going wrong. To do this simply include <errno.h>' and modify the printf to printerrno` which gives specific details about the error:
printf("Chdir error: %d", errno);
For instance chdir() does not only return an error when the directory does not exist, but also for example if you do not have permissions to view the contents of the directory. See the man page for a list of possible errors.
To implement your own shell, you need to take input directly from stdin, not from command-line arguments (argv) from another shell. The basic pattern is like this:
Read input
Execute command
Print results
Loop back to step 1

Process returned -1 (0xFFFFFFFF)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
printf("Transactional Shell Command Test.\n");
while(1) {
printf("Queue:");
char input[500];
fgets (input, 500, stdin);
if(strstr(input, "qb-write")){
printf("These are the commands you have queued:\n");
FILE *cmd = popen("cat /home/$USER/.queueBASH_transactions", "r");
char buf[256];
while (fgets(buf, sizeof(buf), cmd) != 0) {
printf("%s\n",buf);
}
pclose(cmd);
}
system(strncat("echo ",strncat(input," >> /home/$USER/.qb_transactions",500),500));
usleep(20000);
}
return 0;
}
I am attempting to make a concept for a transactional shell, and I'm having it output every command you enter into a file in the user's home directory. It's not completely finished, but I'm doing one part at a time. When I put in any input to the "shell", it crashes. Codeblocks tells me "Process returned -1 (0xFFFFFFFF)" and then the usual info about runtime. What am I doing wrong here?
strncat appends to its first argument in place, so you need to pass it a writable buffer as the first argument. You're passing a string literal ("echo "), which depending on your compiler and runtime environment may either overwrite unpredictable parts of the memory, or crash because it's trying to write to read-only memory.
char command[500];
strcpy(command, "echo ");
strncat(command, input, sizeof(command)-1-strlen(command));
strncat(command, " >> /home/$USER/.qb_transactions", sizeof(command)-1-strlen(command));
system(command);
As with the rest of your code, I've omitted error checking, so the command will be truncated if it doesn't fit the buffer. Also note that repeated calls to strncat are inefficient since they involve traversing the string many times to determine its end; it would be more efficient to use the return value and keep track of the remaining buffer size, but I'm leaving this as a follow-up exercise.
Of course invoking a shell to append to a file is a bad idea in the first place. If the input contains shell special characters, they'll be evaluated. You should open the log file and write to it directly.
char log_file[PATH_MAX];
strcpy(log_file, getenv("HOME"));
strncat(log_file, "/.qb_transactions", PATH_MAX-1-strlen(log_file));
FILE *log_file = fopen(log_file, "a");
…
while (1) {
…
fputs(cmd, log_file);
}
fclose(log_file);
(Once again, error checking omitted.)

sh: 1: Syntax error: "(" unexpected with a C file

I'm getting a confusing error message. I'm running MinGW on Windows XP 32-bit. When I attempt to compile the following code, I get an error message "./hello.c: line 4: Syntax error near unexpected token '('". Line 4 is at int main(...), I can't figure out what unexpected token is "near '('". I've tried using int main(void), but I get the same message. However, if I compile it without the "char string..." and "data = fputs(...)" and have it read from a given text file, it compiles without issue.
What I'm trying to accomplish is to read from a file where the filename is given by an external source, i.e. php. Eventually I'm going to be working this into an Apache module with a parser that I've made, hence the call from php, but I wanted to fool around and build some template code to work with before I got to that part.
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
FILE *fp;
//char string = "JD"; commented out
char data;
//printf("Type in your filename: "); also commented out
//scanf("%s", &argv); also commented out
if(argc >= 2)
{
fp = fopen("sample.txt", "r"); //switched to reading a given file
}
while((data = getchar()) != EOF)
{
fgets(data, sizeof(data), fp);
// data = fputs(string, fp);
}
if (fp==NULL) /* error opening file returns NULL */
{
printf("Could not open player file!\n"); /* error message */
return 1; /* exit with failure */
}
/* while we're not at end of file */
while (fgets(data, sizeof(string), fp) != NULL)
{
printf(data); /* print the string */
}
fclose(fp); /* close the file */
return 0; /* success */
}
Okay, I tried writing a simple "Hello World" program, but I'm still getting the same error message with it which makes me think the error message isn't being caused by my code at all.
#include <stdio.h>
int main(void) //still getting a syntax error before unexpected token '('
{
printf("Hello, world!");
return 0;
}
There is problem with your logic . the "exploit" array would contain "./myotherAAAAAAAAAAAAAAAAAAAA:" which you are passing to system ..so problem are bound to happen
strncpy(command, "./myotherfile ", 9);
only copies the first 9 chars. Replace that with
strcpy(command, "./myotherfile ");
which should do what you want.
P.S. I suspect that you originally had
strncpy(command, "./myfile ", 9);
which would have worked, and you didn't change the 9 when you changed the length of the file name. There are entire books written on why couplings like this are a bad idea and what to do instead. In this case the simplest solution is to use strcpy so you don't need to mention the length.
I think you are trying to run ./motherfile...Then when you concatenate it with "exploit" name becomes "./myotherAAAAAAAAAAAAAAAAAAA " and not "./myotherfile AAAAAAAAAAAAAAAAAAAA", to give space concatenate it with a space first.

Resources