What I am doing: Creating a custom shell for a class project. Just started implementing my fork and exec features.
My Issue: ls works fine until I change directories with cd. After the first cd, ls requires the . to list files in current directory. After that, it seems to lose all functionality.
I've included my code below. Any help (including pointing out obvious newb mistakes) is appreciated. I am in the early stages of grasping these concepts.
int main(){
char *input = malloc(sizeof(char*) * BUFFERSIZE);
char *token;
char **token_array;
int i;
int return_status;
printf("%s", BOILERPLATE);
if (input == NULL){
printf("JASH: Buffer Allocation Failed");
return 1;
}
// Shell Loop
while(1){
i = 0;
printf("JASH:> ");
fgets(input, BUFFERSIZE, stdin);
token_array = malloc(sizeof(char)*strlen(input));
token = strtok(input, " \n()<>|&;");
while(token != NULL){
token_array[i] = token;
token = strtok(NULL, " \n()<>|&;");
i++;
}
// print tokens to confirm correct parsing of input
printf("%s : %s\n", token_array[0], token_array[1]);
//printf("%s\n", token_array[0]);
if(exec_args(token_array) == 0){
// programm exit successfully
free(token_array);
free(input);
return 0;;
}
} // Loop End
// free input buffer before exit
free(token_array);
free(input);
return 0;
}
int exec_args(char ** t){
int i;
if(strcmp(t[0], "exit") == 0
|| strcmp(t[0], "quit") == 0
|| strcmp(t[0], "logout") == 0){
// call built in function for exit
exit(0);
}
if (strcmp(t[0], "cd") == 0){
// call built in function for chdir
if(chdir(t[1]) == 0){
printf("Successfully changed directories to %s\n", t[1]);
return 1;
} else {
printf("-JASH: cd: %s: No such file or directory\n", t[1]);
return 1;
}
}
if (strcmp(t[0], "pwd") == 0) {
char * p = malloc(sizeof(char*) * 100);
getcwd(p, 100);
printf("%s\n", p);
free(p);
return 1;
}
if(strcmp(t[0], "help") == 0){
printf("\n%s\n Help Documentation Coming soon!\n\n", BOILERPLATE);
return 1;
}
if(fork() == 0){
if(execvp(t[0], t)){
perror("JASH");
}
} else {
int status;
wait(&status);
printf("%d", status);
}
return 1;
}
Related
I am new to C programming and currently learning this into a course. I'm facing an issues while trying to practice the below history function.
I'm able to display the shell commands. However, when I type history, the past shell commands are not getting saved into the history buffer.
Can anyone help me to find where I went wrong?
Here is my code:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#define BUFSIZE 20
#define MAX_WORD_IN_LINE 20
int tokenize(char *str, char **args)
{
int i, argc = 0;
char *token;
token = strtok(str," \t\n");
for(i=0; token!=NULL;i++)
{
args[i] = token;
printf("args[%d] = %s\n", i, args[i]);
token = strtok(NULL, " \t\n");
argc++;
}
return argc;
}
void display_strings(char **p)
{
if (p == NULL) return;
while(*p != NULL){
printf("%s\n",*p);
p++;
}
}
int history(char *hist[], int current){
int i = current;
int hist_num = 1;
do {
if (hist[i]) {
printf("%4d %s\n", hist_num, hist[i]);
hist_num++;
}
i = (i + 1) % BUFSIZE;
} while (i != current);
return 0;
}
int main(void){
char *args[MAX_WORD_IN_LINE];
char buffer[BUFSIZE];
char *hist[BUFSIZE];
int i,current=0;
pid_t pid;
int argc;
for(i=0;i<BUFSIZE;i++)
hist[i]= NULL;
while(1) {
memset(args,0,MAX_WORD_IN_LINE);
printf("osh> ");
fgets(buffer, BUFSIZE, stdin);
argc = tokenize(buffer, args);
//display_strings(args);
// skip on empty command
if (argc == 0) continue;
if (strcmp(args[0],"quit") == 0) break;
else if (strcmp(args[0], "hello") == 0) printf("Hello there. How are you?\n");
else if (strcmp(args[0],"history")==0) history(hist,current);
else {
pid = fork();
if (pid == 0) {
hist[current]=strdup(args[0]);
current++;
execvp(args[0], args);
return 0;
}
You need to make a copy of the string that args[0] points to when you save it in hist. Currently, you're just assigning the pointer to the current args[0], and it will be overwritten by the next command. When you print the history, you'll just get the last command repeatedly. So use:
hist[current] = strdup(args[0]);
I was presented with this code as an example of how to make a 'mini shell' but I have no idea what it is doing-- I understand most things in the main function, but the functions above it initializing I'm really quite lost on.
Any basics on how a shell works and some of the lower level functions in C would be much appreciated.
log_t Log;
void printLog(log_t *l){
log_entry_t *tmp = l->tail;
while(tmp != NULL){
printf("%s\n", tmp->value);
tmp = tmp->prev;
}
}
int countArg(char *line){
unsigned int i, count = 0;
for(i = 0; i < strlen(line); i++){
if(line[i] == ' ')
count++;
}
return count + 1;
}
char **makeargv(char *line){
char *buff;
char **retv;
unsigned int i, count, arg_num = 0;
buff = malloc((strlen(line) + 1) * sizeof(char));
strcpy(buff, line);
arg_num = countArg(buff);
retv = malloc((arg_num + 1) * sizeof(char*));
retv[arg_num] = NULL;
retv[0] = buff;
for(i = 0, count = 1; buff[i]!='\0'; i++){
if(buff[i] == ' '){
buff[i]='\0';
retv[count] = buff + i + 1;
count++;
}
}
return retv;
}
int main()
{
log_init(&Log);
char * line;
while(1){
size_t size;
char * cwd = NULL;
char * query = NULL;
char * match = NULL;
int f_exit = 0,
f_nbi=0,
f_match=0,
f_sys = 0,
f_path=0;
// print out the requested prompt
cwd = getcwd(NULL,0);
printf("$(pid=%d)%s$ ", getpid(), cwd );
free(cwd);
line = NULL;
fflush(stdout);
getline(&line, &size, stdin);
line[strlen(line)-1] = '\0';
while(1){
if(strcmp(line, "exit") == 0){
if(f_match) printf("%s matches %s\n", query, line);
f_exit = 1;
printf("Command executed by pid=%d\n",getpid());
log_destroy(&Log);
break;
}
else if(strncmp(line, "cd ", 3) == 0){
if(f_match) printf("%s matches %s\n", query, line);
printf("Command executed by pid=%d\n",getpid());
log_push(&Log, line);
if(chdir(line + 3) != 0){
printf("%s: No such file or directory\n",(line + 3));
}
break;
}
else if(strcmp(line, "!#") == 0){
if(f_match) printf("%s matches %s\n", query, line);
printf("Command executed by pid=%d\n",getpid());
printLog(&Log);
break;
}
else if(strstr(line,"!")==line){
if(f_match) printf("%s matches %s\n", query, line);
query = line+1;
match = log_search(&Log, query);
if(match==NULL){
printf("Command executed by pid=%d\n",getpid());
printf("No Match\n");
break;
}
else{
f_match = 1;
printf("Command executed by pid=%d\n",getpid());
line = malloc((strlen(match)+1)*sizeof(char));
strcpy(line, match);
continue;
}
}
else{
f_nbi = 1;
log_push(&Log,line);
if(strstr(line,"/"))
f_path = 1;
break;
}
}
if(f_exit)
break;
if(f_nbi){
pid_t pid = fork();
if(pid == 0){ //child
if(f_match){
printf("%s matches %s\n", query, line);
free(query-1);
}
printf("Command executed by pid=%d\n",getpid());
char **argv = makeargv(line);
f_sys = 0;
if(f_path)
f_sys = execv(argv[0],&argv[0]);
else
f_sys = execvp(argv[0],&argv[0]);
if(f_sys == -1)
printf("%s: not found\n",line);
free(argv[0]);
free(argv);
log_destroy(&Log);
exit(0);
}
else { //parent
waitpid(pid, NULL, WUNTRACED);
if(f_match)
free(query-1);
}
}
free(line);
line = NULL;
}
if(line != NULL){
free(line);
line = NULL;
}
return 0;
}
The printLog() function is simply printing all the data from a linked list of records in a structure type given the name log_t by a typedef. It starts at the tail of the list and works backwards. It is not clear whether the list is single-linked or double-linked, but the prev usually only appears in a double-linked list.
The countArgs() function is a crude way of determining how many arguments are present on a command line. It is crude because it does not take into account quotes or multiple adjacent spaces. However, those merely mean it overestimates the number of arguments, which is not serious. It doesn't recognize tabs as separators, which is unusual for a shell.
The makeargv() function splits a command line into a series of space separated words and returns the list of words to the calling function. It effectively parses the command line, making sure there's a null pointer at the end of the command line. Again, it is simplistic, but sufficient. It is also inconsistently named compared with the other two functions.
So I have this code for a shell:
#include <stdio.h>
#include <stdlib.h>
#define MAX_NUM_ARGS 256
#define SIZE 256
void orders(char *command[SIZE]);
int main() {
char buffer[SIZE]= "";
//char input_args[MAX_NUM_ARGS];
char **input_args = NULL;
int i = 0;// counting variable
int blah = 0;
printf("Welcome to the AY shell.\n");
while(1){
//initialize array of strings
//first free any prevously allocated memory
if (input_args != NULL)
{ //memory has been allocated free it
for (i = 0; i <MAX_NUM_ARGS; i++)
{
free(input_args[i]);
}
}
//free array of strings
free(input_args);
//new allocate memory
input_args = (char**) malloc(sizeof(char*) * MAX_NUM_ARGS);
//check return value for error
if (input_args == NULL)
{
printf("We are out of memory. =(\n");
continue;
//print error: out of memory, exit with a error code
exit(0);
}
//allocate memory for each string
for (i = 0; i <MAX_NUM_ARGS; i++)
{
input_args[i]= (char*)malloc(sizeof(char) * MAX_NUM_ARGS);
if(input_args[i] == NULL)
{//error
printf("Error, the input is empty.");
continue;
}//end of if statement
}//end of for loop
printf("~$: "); //prompts the user for input
fgets(buffer, sizeof(buffer), stdin);
//if the user types in exit, quit
if (strcmp(buffer, "exit\n") == 0){
exit(0);
} //end of if statement
//if user types in clear, wipe the screen and repeat the lop
else if(strcmp(buffer, "clear\n")==0){
system("clear");
continue;
}//end of else if
//should the user punch in nothing, repeat the loop
else if (strcmp(buffer, "\n") == 0) {
continue;
}//end of else if
input_args[1] = NULL;
for (i = 0; i < SIZE; i++) {
if(buffer[i] != '\n' && buffer[i] != ' ' && buffer[i] != '\t'){
input_args[0][i] = buffer[i];
} //end of if statement
else{
input_args[0][i] = '\0';
}//end of else statment
}//end of for loop
//if the input doesn't fall under the conditionals above, execute orders.
orders(input_args);
} //end of while loop
return 0;
}//end of main function
void orders(char *command[SIZE]){
//handles the commands of the shell
int retval = 0; //return value
int pid = 0;
int childValue = 0;
pid = fork();
if (pid != 0){
// printf("I'm the parent, waiting on the child.\n");//debug
pid = waitpid(-1, &childValue,0);
// printf("Child %d returned a value of %x in hex.\n", pid, childValue);
return;//return backs to the main prompt
}//end of if statement
else{
// printf("I am the first child.\n");
retval = execvp(command[0], command);
exit(2);
if (retval != -1){
//print error!
printf("Invalid command!\n");
exit(2);
}
}//end of else block
}//end of orders function
Now, it executes clear, exit, and single word commands just well, like ls, or pwd. However, multi-line commands such as "vim " don't work, nor changing directories.
What am I doing wrong?
I'm suspecting the retval = execvp(command[0], command); is causing problems, but I'm not too entirely sure. Any thoughts? I don't want a direct answer, since this is homework, just a push in the right direction.
This section:
input_args[1] = NULL;
for (i = 0; i < SIZE; i++) {
if(buffer[i] != '\n' && buffer[i] != ' ' && buffer[i] != '\t'){
input_args[0][i] = buffer[i];
} //end of if statement
else{
input_args[0][i] = '\0';
}//end of else statment
}//end of for loop
limits input_args to only have the first index be used. I assume this is where you would find a way to have a j++; inside the else clause and use input_args[j][i] or something similar...
And your last comment matches this, since your retval = execvp(command[0], command); is also only using the first item from the list.
for my class we are to implement a shell with output redirection. I have the output redirection working, except my first command is always corrupted see:
$ echo this doesn't work
H<#?4echo
No such file or directory
$ echo this does work
this does work
but every command afterwards seems fine. What technique do I use to find the bug that is causing this problem?
I think it has something to do with not fflushing properly. I sprinkled it around my code (which was stupid) to see if it would help during the loop but it did not. I've also tried printing out my OrderedIds list which is just a list of commands to check if I could find H<#?4 anywhere, but even when I initialized it, it did not work.
Thanks for your help.
#define LENGTH 1000
#define MAXCMD 11
#define MAX_STR_LEN 20
void init(char *temp);
void clean(char **orderedIds);
void init_pid(int *temp);
void reap(int *temp,int ret_status);
void jobs(int *pid_list, char **orderedIds);
int ioRedir(char **orderedIds);
void reap(int *temp,int ret_status){//chainsaws all zombies
int a;
for (a=0; a<LENGTH; a++ ){
waitpid(temp[a],&ret_status,WNOHANG) == temp[a];
}
}
void init(char *temp){//Function to initialize/reset the cmd array
int i;
for(i=0; i<LENGTH; i++){
temp[i] = 0;
}
}
void init_pid(int *temp){//Function to initialize/reset the pid list
int i;
for(i=0; i<LENGTH; i++){
temp[i] = -777;
}
}
void clean(char **orderedIds){//garbage collection
int i;
for(i=0; i<MAXCMD; i++){
free(orderedIds[i]);
}
free(orderedIds);
}
void jobs(int *pid_list, char **orderedIds){//function to check for background proccesses
printf("Jobs:\n");
int y;
for(y=0; y<LENGTH; y++){
if(kill(pid_list[y], 0) == 0){
printf("%d\n", pid_list[y]);
}
}
clean(orderedIds);
printf("$ ");
}
int ioRedir(char **orderedIds){
int i;
for ( i = 0; i<MAXCMD; i++){
if(orderedIds[i] == NULL){
return -1;
}
if(strcmp(orderedIds[i],">")==0){
return (i+1);
}
}
}
int main (int argc, char *argv[], char *envp[])
{
char temp[LENGTH];
char * tok;
char c = '\0';
int saved_stdout;
int pid_list[LENGTH];
int ret_status;
int numFile;
int pid_counter = 0;
int outputfd = -1;
char outputFile[MAX_STR_LEN];
pid_t pid;
printf("$ ");
int i, j, y, background= 0;
init_pid(pid_list);
while(c !=EOF) { //while not ^D // Source: LinuxGazzette Ramankutty
outputfd = -1;
fflush(0);
c = getchar();
if(c=='\n'){ //entered command
reap(pid_list, ret_status);
char **orderedIds = malloc(MAXCMD * sizeof(char*));
for (i=0; i<MAXCMD; i++){
orderedIds[i] = malloc(MAXCMD * sizeof(char*));
}
int k=0;
tok = strtok(temp, " \n\t\r");
while (tok !=NULL){
strcpy(orderedIds[k], tok);
k++;
tok = strtok (NULL, " \n\t\r");
}
orderedIds[k] = NULL; //END with NULL
init(temp); //initialize the array
if(orderedIds[0] ==NULL){
printf("\n$ ");
continue;
}
numFile = ioRedir(orderedIds);
if(strcmp(orderedIds[0],"exit")==0){// if exit
printf("now exiting...\n");
break;
}
if(strcmp(orderedIds[k-1], "&")==0){//if background
orderedIds[k-1] = NULL;
background = 1;
}else background = 0;
if(strcmp(orderedIds[0], "jobs") == 0){//if jobs command
jobs(pid_list, orderedIds);
continue;
}
if(strcmp(orderedIds[0], "cd") == 0){ //if change directory command
chdir(orderedIds[1]);
printf("$ ");
continue;
}
pid = fork();
if (pid!=0 && background == 1)
{
//go to end of list in pid and put it in
pid_list[pid_counter] = pid;
pid_counter++;
printf("To the background: %d\n", pid);
} else if (pid==0 && background == 1) {
fclose(stdin); //close child's stdin
fopen("/dev/null", "r"); //open a new stdin that is always empty.
if(execvp(orderedIds[0], orderedIds)){
printf("%s\n", orderedIds[0]);
puts(strerror(errno));
exit(127);
}
}
if (pid != 0 && !background){
//printf("Waiting for child (%d)\n", pid);
fflush(0);
pid = wait(&ret_status);
} else if (pid == 0 && !background) {
if(numFile > 0){
strncpy(outputFile, orderedIds[numFile], strlen(orderedIds[numFile]));
numFile = 0;
//open the output file
outputfd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU | S_IRGRP | S_IROTH);
if (outputfd < 0) {
exit(EXIT_FAILURE);
}
//close STDOUT
if(close(STDOUT_FILENO) < 0 ){
perror("close(2) file: STDOUT_FILENO");
close(outputfd);
exit(EXIT_FAILURE);
}
//use dup to rerout the output
if(saved_stdout = dup(outputfd) != STDOUT_FILENO){
perror("dup(2)");
close(outputfd);
exit(EXIT_FAILURE);
}
close(outputfd);
}
if (execvp(orderedIds[0], orderedIds)){
printf("%s\n", orderedIds[0]);
puts(strerror(errno));
exit(127);
}
}
dup2(saved_stdout,outputfd);
clean(orderedIds);
fflush(0);
printf("$ ");
} else {
strncat(temp, &c, 1);
}
}
fflush(0);
return 0;
}
The reason for the garbage is that you never initialized temp to an empty string at the beginning of main(). You call init(temp) after processing each command.
There are lots of other problems in your code:
orderedIds[i] = malloc(MAXCMD * sizeof(char*));
Since orderedIds[i] is an array of char, not char*, you should multiply the size by sizeof(char). Also, it's not clear why you're using MAXCMD as the size -- on the previous line this was the maximum number of words on a line, not the number of characters in a word.
strcpy(orderedIds[k], tok);
You should use strncpy() to ensure that you don't copy more than the size of orderedIds[k].
Another option would be not to preallocate all the orderedIds[i] in the first place. Instead of using strcpy(), use strdup() and assign this to orderedIds[k]; if you do this, you have to remember to free() all these strings.
A third option is not to copy the strings at all. Just assign the pointers returned by strtok() to orderedIds[k]. But in this case you mustn't call init(tmp) until after you've forked.
strncpy(outputFile, orderedIds[numFile], strlen(orderedIds[numFile]));
The limit should be the size of outputFile, not the length of orderedIds[numFile]. strncpy() will never copy more than the length of the source, you need to tell it the maximum size of the destination to prevent a buffer overflow.
outputfd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU | S_IRGRP | S_IROTH);
if (outputfd < 0) {
exit(EXIT_FAILURE);
}
You should call perror() to report the reason that open() failed.
puts(strerror(errno));
Call perror(), like you do elsewhere.
I have a code that scans all the files in a directory for targeted words, and prints them out into a new file. The problem right now is after the while loop reads a file and stores a variable into the string (ex. customer), if the next file being read does not have the targeted word, it still displays the result stored in the string from the previous file. My goal is to make it display "N/A" if the current file does not have the target word.
I have tried a few ways to clear the string at the end or beginning of the while loop, but none of them work most of them just gives me a coredump error. Running out of ideas, any help would be much appreciated!
Code (shortened for easier reading):
int main(int argc, char** argv)
{
char directory[100];
char buff[100];
char delims[] = " :=";
char* result = NULL;
char* customer;
char* device;
char* buffer;
int i = 0;
DIR* FD;
struct dirent* in_file;
int c = 0;
printf("Enter directory:");
scanf("%s",directory);
FILE* ft = fopen("workorderlist.csv", "w"); /* Open file to write to*/
if (ft == NULL)
{
puts("Cannot open target file");
exit(1);
}
fprintf (ft, "Work Order,Customer,Device,Test_Prog,Software,DUT_board_id,Corl box\n");
/* Open Directory */
if (NULL == (FD = opendir(directory)))
{
puts("Cannot open directory");
return 1;
}
while ((in_file = readdir(FD)))
{
if (!strcmp (in_file->d_name, "."))
{
continue;
}
if (!strcmp (in_file->d_name, ".."))
{
continue;
}
/* Open files to read from */
buffer = (char*)malloc(100);
sprintf(buffer, "%s/%s", directory, in_file->d_name);
size_t len = strlen(buffer);
if (len >= 4 && memcmp(buffer + len - 4, ".wor", 4) == 0) /* checks if file ends with .wor */
{
FILE* fs = fopen(buffer, "r"); /* open file to read */
if (fs == NULL)
{
puts("Cannot open source file");
return 1;
}
/* Scanning each file for targeted words: */
while (fgets(buff, 100, fs) != NULL)
{
result = strtok( buff, delims );
while (result != NULL)
{
if ((strcmp(result, "Customer") == 0))
{
result = strtok(NULL,delims);
customer = (char*)malloc((strlen(result)+1)*sizeof(char));
strcpy(customer, result);
for (i = 0; i < strlen(customer) + 1; i++)
{
if (customer[i] == '\n')
{
break;
}
}
customer[i] = ' ';
}
if (strcmp(result, "device") == 0)
{
result = strtok(NULL, delims);
device = (char*)malloc((strlen(result) + 1) * sizeof(char));
strcpy(device, result);
for (i = 0; i < strlen(device) + 1; i++)
{
if(device[i] == '\n')
{
break;
}
}
device[i] = ' ';
}
result = strtok(NULL,delims);
}
}
if (customer == '\0')
{
customer = "N/A";
}
if (device == '\0')
{
device = "N/A";
}
fprintf(ft, "%s,%s,%s,%s,%s,%s,%s\n",
in_file->d_name, customer, device, testprog,
software, dutboardid, corlbox);
printf(in_file->d_name);
printf("\n");
fclose (fs) ;
c++;
}
}
printf("Total Workorders Found: %d (Info saved to workorderlist.csv)\n", c);
fclose(ft);
return 0;
}
First at all, customer/device are strings. You should not be doing == for it comparison. You can, for example, compare the first char of the string: device[0] == '\0';
You should do string initialization before the loop starts.
You can achieve this by using strcpy with a known value or any other string manipulation function. The value that you use to initialize the string before the loop is the one you gonna test with strcmp or similar later.
Is like with ints or any other C data type, but you need manipulation functions instead.
By the way, haven't you posted your read file loop in a question here too?
Hope this helps.