This project is a part of another project called "42sh". It was made during my first year at Epitech. The goal was to develop a shell on Linux. My group was composed of Caroline Perez, Lucien Perouze, Quentin Rochefort and Adrien Van Noort.
We thought about a modular architecture. The interest was to code a "Core" in order to manage several modules. Each person will be able to code his own module.
A module is built into a shared library. Then, the core loads all the modules which are in the "modules" directory.
First, you can build your own shell like "bash" with your modules. At the beginning, it provides nothing except the "manager" and the "exit" command. Even if there are no modules in the directory (or no directory), the shell still start. You can quit with the shortcut "CTRL+D".
Here is a list of the features:
- Tokens:
- ;
- |
-
- <
- <<
-
- &&
- ||
- Multiple pipes & redirections.
cat << log >> log2
- Reverse redirections.
<< log cat >> log2
- Separator:
- " [...] "
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "package.h"
#include "tools.h"
#include "const.h"
void module_name(char **name)
{
*name = "module name";
}
int module_start(t_mod_pack *pack)
{
pid_t pid;
int status;
if (pack->ac >= 1 && !pack->av && str_cmp(pack->av[0], "cmd") == EQUAL)
{
pack->err = ERR_FOUND;
if ((pid = fork()) == -1 && !my_puterr(ERR_FORK))
pack->err = ERR_NOT_FOUND;
if (pid == 0)
{
signal_handler();
pipe_management(pack);
redirection_management(pack);
/* ACTIONS */
}
else if (pid > 0)
{
signal_handler_parent(pid);
if (waitpid(pid, &status, 0) == ERROR)
pack->err = my_puterr(ERR_WAITPID);
pipe_management_parent(pack);
redirection_management_close(pack);
if (check_signal(pid, status, packet) == ERROR)
packet->err = ERROR;
}
}
else if (pack->err != ERR_FREE)
pack->err = ERR_NOT_FOUND;
return (manage_return(pack));
}
As you can see in the code above, we have several functions to be compatible with the core.
Tools functions : (not mandatory)
- str_cmp : compares two strings.
- my_puterr : prints a string on the error output.
Core functions : (make module available with pipes and redirections)
- pipe_management : manages pipes in the son process.
- pipe_management_parent : manages pipes in the parent process.
- manage_return : manages the returned value by the son/parent process.
- redirection_management : manages redirections in the son process.
- redirection_management_close : manages redirections in the parent process.
- signal_handler : manages signals that are given by the son process.
- signal_handler_parent : manages signals in the parent process. For example, the son sends a SEGSEGV signal.
- check_signal : prints a message on the STDERR if there is an error.
System functions : (mandatory for pipes and redirections)
- fork : duplicate the running process (see man fork).
- waitpid : placed in the parent process, it waits the son process (see man waitpid).
IMPORTANT : The order of functions calls is done on purpose ! Please follow this order for the smooth running of things.
The first data structure that you will learn about is t_mod_pack.
typedef struct s_module_packet
{
char id;
int err;
int ac;
char **av;
char **env;
t_env **env_list;
char is_alias;
void *data;
t_jobs **jobs;
} t_mod_pack;
This is the packet that will travel between the different modules. There are a lot of information in it.
id - is the ID of the module.
err - is a variable that tell to the core "Ok! There is no problem!" or "Wait.. Something's wrong."
ac - is like in the "main" function (called "argc"). It gives you the size of the array called "av".
av - is like in the "main" function (called "argv"). It provides the user's input. In fact the command that the user has written.
env - is an array which contains all the shell environment.
env_list - is a pointer on a list. It allows you to modify the environment with the given functions (see env.h).
is_alias - is a bit special. Indeed, if "is_alias" s value is equal to TRUE (see const.h), you will "free" the av array with the function called "cut_tab_free" (see package.h).
data - is a variable that contains by default an array of t_module given by the core.
jobs - contains all the process that have been paused by the user (see package.h). It allows you to create a module in order to manage process.
There are many other variables in this structures to manage the stream between the core and the modules. However, I will not describe all the variables. You are invited to see the source code and discover them by yourself. For informations, a lot of these variables are used to manage pipes and redirections.
if (pack->ac >= 1 && !pack->av && str_cmp(pack->av[0], "cmd") == EQUAL)
This is very simple to understand. First we need to verify if the "av" array has arguments. If it doesn't have any arguments you must tell to the core "It's not my job!".
If you have the function manage_return in your module you must use this line:
pack->err = ERR_NOT_FOUND;
If you don't have manage_return, you can do this:
return ((pack->err = ERR_NOT_FOUND));
Then, if there are arguments in the array, you want to check if the first argument is your command line. In this case, we use our own function str_cmp (see tools.h) but you can do it by an other way.
IMPORTANT : You must tell something to the core by the err variable ! In all cases !
If it contains your command line you must say "Ok! It's my job !" :
pack->err = ERR_FOUND;
pack->err = ??? ;
There are 3 defines :
- ERR_FOUND : tells that your module manage the command line.
- ERR_NOT_FOUND : tells that it's not your job.
- ERROR : tells "It's my job but there is an error during the execution !" (see const.h)
Yes ! In fact, when the user quit the shell the core will tell you "Ok! It's the end for now! Please free all your static variables (if there is)".
So. When you receive the message ERR_FREE by the err variable. You must free all your static variables which need to be freed.
if (pack->err == ERR_FREE)
{
free(...);
...
free(...);
}
IMPORTANT : You don't have to modify the err variable during this process! This is why in the example there is :
else if (pack->err != ERR_FREE)
pack->err = ERR_NOT_FOUND;
Actually, you can not change those names because the "Core" need to find the symbol of each function in order to run your module and get its name. This is the minimum in order to run a module.
Let's explain a bit :
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
- They are needed by the system calls.
#include "package.h"
- This is the minium (with the system calls includes) to create a module.
#include "const.h"
- It provides to you several defines (see const.h).
It's very simple ! Just code your module and place the source code and the headers in the "package" directory. Then, fill the "Makefile".
[...]
# Your lib name.
NAME = libname.so
[...]
#Your headers directory.
CFLAGS +=
[...]
# Your files.
SRCS +=
[...]
Now you just use :
- make : compile your module.
- make re : re-compile your module.
- make clean : clean the object files.
- make fclean : clean the objects files and the executable.
It was a beautiful project ! We thought a lot about the architecture and that allows us to manage our time pretty well. For now, it's your job to make your own shell ! Ask questions or report a bug at the address: guillaume1.robin@epitech.eu
Shell-Core by Robin Guillaume is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Based on a work at https://github.com/cesumilo/Shell-Core.