cshell/main.c

304 lines
7.7 KiB
C

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#define true 1
#define false 0
#define LEN 512
#define HOST_USER_LEN 20
#define PATH "/bin/"
#define ERROR -1
#define EXIT_1 256
int getHostName(char* host);
int getUserName(char* name);
int runExec(char** args, int writeToFile);
void PrintTokens (char** tokens);
char** ReadTokens(FILE* stream, int* writeToFile) ;
void FreeTokens(char** tokens) ;
void checkIfQuit(char* word);
int getFileNameIndex(char** args);
void freeRemainderInArr(char** args, int beforLast);
void sigHandler(int sig);
//GLOBAL INT - THIS WILL HOLD CHILD PID OR -1 IF THERE IS NO CHILD
int pidOfChild = -1;
int main()
{
int exitCode = 0;
char* host_n = (char *)malloc(sizeof(char));
char* user_n = (char *)malloc(sizeof(char));
char** args;
int writeToFile = false;
if(getHostName(host_n) == ERROR)
goto quit;
if(getUserName(user_n) == ERROR)
goto quit;
signal(SIGINT, sigHandler); // Change the default SIGINT handler
while(1)
{
printf("%d %s@%s$ ",exitCode, user_n, host_n); // Print Prompt
args = ReadTokens(stdin, &writeToFile); // get user command and tokenize it to args, check if there is a redirect to file.
if(args != NULL) // if user did enter a command do the following
{
exitCode = runExec(args, writeToFile); // fork and run the process, return child exit code
FreeTokens(args); //in case of error while trying to create process - this method will free the memory already
if(exitCode == ERROR) // in case we need to exit.
goto quit;
}
writeToFile = false; // set the "boolean" back to default
}
quit:
free(host_n);
free(user_n);
return 0;
}
/**
* Atemting to get the host name from the system
* @param host - the string that will hold the host name
* @return 0 if all went well, -1 (ERROR) otherwise
*/
int getHostName(char* host)
{
if(gethostname(host, HOST_USER_LEN) != 0)
{
fprintf(stderr, "%s\n", "Error: while getting host name");
return ERROR;
}
return 0;
}
/**
* Atemting to get the user name from the system
* @param host - the string that will hold the user name
* @return 0 if all went well, -1 (ERROR) otherwise
*/
int getUserName(char* name)
{
if(getlogin_r(name, HOST_USER_LEN) != 0)
{
fprintf(stderr, "%s\n", "Error: while getting User name");
return ERROR;
}
return 0;
}
/**
* This function will atempt to create a new Process.
* and will return the exit status code of the child
* @param args - an array of strings that contains all the arguments from the user
* @return int - exit status of child
*/
int runExec(char** args, int writeToFile)
{
int status;
pid_t pid;
char* path = (char *)malloc(sizeof(char));
char* exe = (char *)malloc(sizeof(char));
strcpy(exe,args[0]);
strcpy(path, PATH);
strcat(path, exe);
pid = fork();
if(pid < 0)
{
fprintf(stderr, "%s\n", "Error: Could not create a new process");
return ERROR;
}
else if(pid == 0) // child
{
if(writeToFile == true)
{
int i_lastArg = getFileNameIndex(args);
char* fileName = (char *)malloc(strlen(args[i_lastArg])*sizeof(char));
strcpy(fileName, args[i_lastArg]); //saving file name before cleaning the array
freeRemainderInArr(args, i_lastArg);
int fd = open(fileName, O_RDWR | O_CREAT , S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP| O_TRUNC);
if(fd >= 0)
{
dup2(fd, 1); // make stdout go to file
close(fd);
}
else
{
fprintf(stderr, "%s%s%s\n", "Unable to open file ",fileName," : printing out put to default stdout:");
}
}
if(execv(path, args) != 0)
{
fprintf(stderr, "%s%s%s\n", "cshell: ", args[0] ,": command not found");
exit(1); //child error - chield needs to be closed manually. exit code 255;
}
}
else //pid > 0 father
{
pidOfChild = pid;
waitpid(pid,&status,0);
}
free(path);
free(exe);
if(status == EXIT_1) //in my computer exit(1) returns 256 - ex demands wanted 255
return status-1;
return status;
}
/**
* this func will read a full line and will store all the arguments in an array (of char*).
* if the line will have only 1 word - it will call afunction to check if the word is "quit".
*
* @param stream FILE* - in our case stdin
* @return if all went well , an Array of arguements from the input line.
*/
char** ReadTokens(FILE* stream, int* writeToFile)
{
char** returnArr;
char input[LEN]; // the var to hold the line
char temp[LEN]; // will be a copy of the input line - so we can figure how many words we have
int wordCnt = 0; // word counter
int i = 0; //index for second iteration
fgets(input, LEN, stream); // get the linei
strcpy(temp, input); // copy the line
//strkot first iteration to count words (corrups temp var)
char* tok;
tok = strtok(temp," \n");
while (tok != NULL)
{
wordCnt++;
tok = strtok(NULL, " \n");
}
if(wordCnt == 0) //if there are no "words" in input - return null
return NULL;
returnArr = (char**)malloc((wordCnt + 1)*sizeof(char*)); //allocates the memory for the array
if(returnArr == NULL) //check if the malloc func finished successfuly
{
fprintf(stderr,"%s\n", "ERROR: unable to allocate memory.");
exit(1);
}
//strtok second iteration - puts all words in an index of the array
tok = strtok(input," \n");
while (tok != NULL)
{
returnArr[i] = (char *)malloc(strlen(tok)*sizeof(char));
strcpy(returnArr[i], tok);
if(wordCnt == 1)
checkIfQuit(tok);
if(i == wordCnt-2) // one before the last argument. to see if to put output into file.
if(strcmp(">", returnArr[i]) == 0)
*writeToFile = true;
tok = strtok(NULL, " \n");
i++;
}
returnArr[wordCnt] = NULL; // the last index will be NULL - so we know where the end is.
return returnArr;
}
/**
* if there is only one word in the input nile the ReadTokens func will call this func.
* it will check if user want to exit our program
*
* @param word - the one word to check
*/
void checkIfQuit(char* word)
{
if(strcmp(word, "exit") == 0)
exit(0);
}
/**
* this func will print an array of char*, it will know when to finish if the
* last index is NULL!
* @param tokens - char* Array
*/
void PrintTokens (char** tokens)
{
int i;
for (i = 0; tokens[i] != NULL; i++)
{
printf("%s\n", tokens[i]);
}
}
/**
* this will make sure no memory leeks will occurd.
* runs through all indexes change them to null and then free them.
* after that it will fraa the array pinter itself.
* @param tokens - an array to free
*/
void FreeTokens(char** tokens)
{
int i = 0;
while(tokens[i] != NULL) // free all the indexes except the last one (NULL)
{
tokens[i] = NULL;
free(tokens[i]);
i++;
}
free(tokens[i]); // free the last index
tokens = NULL; // free the array
free(tokens);
}
/**
* returns the index of the File name to redirect output to.
* @param args - an array of all the arguments
* @return int - index whare the fileName is in
*/
int getFileNameIndex(char** args)
{
int i = 0;
while(args[i] != NULL)
i++;
//we are at the end of the array.
return i-1; //the last arg must be the file name
}
/**
* this function will modify th args array, in case of redirecting.
* in that case, the 2 last args are ">" -> "filename"
* so, the function will put Null insted of ">", and will free
* the unneeded array cells.
* @param args - the array of arguments
* @param beforLast - the index of ">"
*/
void freeRemainderInArr(char** args, int beforLast)
{
free(args[beforLast+1]);
args[beforLast] = NULL;
free(args[beforLast]);
args[beforLast - 1] = NULL;
}
/**
* This function will be calld when the SIGINT is invoked.
* only if there is a child, the "father" will kill it.
* @param sig -
*/
void sigHandler(int sig)
{
if(pidOfChild >= 0)
kill(pidOfChild, SIGINT);
pidOfChild = -1;
}