Ecriture d'un shell

Published 10-12-2018

Un shell est la couche le plus haut niveau du système Unix.

Pour faire simple, un shell est un programme qui prend en input une commande, la parse et l’exécute.

Nous allons diviser le travaillons en plusieurs parties :

  • Récupérer en boucle l’entrée de l’utilisateur
  • Parser l’entrée utilisateur
  • Executer la commande
  • Coder les builtins
  • Gestion de l’environnement

Tout au long de ce tutorial, la compilation se fera comme suit :

clang -Weverything minishell.c -o minishell

1) Boucle principale

On commence par faire une boucle dans laquelle on lit STDIN (la commande de l’user)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int	main()
{
	char	*buffer = NULL;
	size_t	buf_size = 2048;

	// alloc buffer qui stockera la commande entree par l'user
	buffer = (char *)calloc(sizeof(char), buf_size);
	if (buffer == NULL) {
		perror("Malloc failure");
		return (EXIT_FAILURE);
	}

	// ecriture d'un prompt
	write(1, "$> ", 3);

	// lecture de STDIN en boucle
	while (getline(&buffer, &buf_size, stdin) > 0) {
		printf("cmd = %s\n", buffer);
		write(1, "$> ", 3);
	}

	printf("Bye \n");
	free(buffer);
}

Nous avons un programme qui affiche un prompt et qui stocke l’entrée de l’utilisateur en boucle. On envoie EOF (Ctrl+D au shell pour quitter)

Output :

$> ls -la
cmd = ls -la

$> cd ~/
cmd = cd ~/

$> pwd
cmd = pwd

$> Bye 

Il faut maintenant faire une fonction qui parse la commande.

2) Parsing

Prenons pour exemple la commande :

ls -la /

Nous avons le nom du binaire (ls) et ses arguments.

la commande pourrait aussi être :

$>  ls       -la      /  

Nous allons écrire une fonction qui va stocker notre commande (sans les espaces) dans un char ** Ce qui donnera :

[ls][-la][/]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

static char	**split(char *raw_cmd, char *limit)
{
	char	*ptr = NULL;
	char	**cmd = NULL;
	size_t	idx = 0;

	// split sur les espaces
	ptr = strtok(raw_cmd, limit);

	while (ptr) {
		cmd = (char **)realloc(cmd, ((idx + 1) * sizeof(char *)));
		cmd[idx] = strdup(ptr);
		ptr = strtok(NULL, limit);
		++idx;
	}
	// On alloue un element qu'on met a NULL a la fin du tableau
	cmd = (char **)realloc(cmd, ((idx + 1) * sizeof(char *)));
	cmd[idx] = NULL;
	return (cmd);
}

static void	free_array(char **array)
{
	for (int i = 0; array[i]; i++) {
		free(array[i]);
		array[i] = NULL;
	}
	free(array);
	array = NULL;
}

Comme on alloue dynamiquement notre char **, on fait une fonction (free_array) qui va libérer notre allocation.

Nous sommes maintenant prêt à exécuter notre commande avec execve

3) Exécution

Pour exécuter notre commande nous allons utiliser le syscall execve.

Nous devons utiliser le syscall fork pour crée un nouveau processus et lancer notre commande dans ce dernier.

Ce qui donne ça :

static void	exec_cmd(char **cmd)
{
	pid_t	pid = 0;
	int		status = 0;

	// On fork
	pid = fork();
	if (pid == -1)
		perror("fork");
	// Si le fork a reussit, le processus pere attend l'enfant (process fork)
	else if (pid > 0) {
		// On block le processus parent jusqu'a ce que l'enfant termine puis
		// on kill le processus enfant
		waitpid(pid, &status, 0);
		kill(pid, SIGTERM);
	} else {
		// Le processus enfant execute la commande ou exit si execve echoue
		if (execve(cmd[0], cmd, NULL) == -1)
			perror("shell");
		exit(EXIT_FAILURE);
	}
}

Si on compile et on exécute le code, voilà ce qu’il se passe :

$> ls
shell: No such file or directory
$> /bin/ls
README.md  main.c  mainn.c  minishell
$> Bye 

En envoyant une commande simple comme ls à notre shell, execve nous renvoie -1 et perror affiché No such file or directory.

Le premier argument d'execve doit être le chemin absolu du binaire à exécuter.

Pour lancer une commande sans donner le chemin absolu, nous devons chercher ou se trouve le binaire ls, concaténer le path + le nom du binaire et enfin le passer en premier argument à execve

Pour trouver ou se trouve un programme, nous devons utiliser la variable d’environnement PATH.

Si on exécute la commande :

$> echo $PATH

Nous allons avoir un output qui ressemble à celui-là :

/bin:/usr/bin:/usr/local/bin

Il s’agit des dossiers (séparer par ‘:‘) ou notre Shell va chercher notre binaire à exécuter.

Nous devons maintenant écrire la fonction qui va concaténer notre path et le binaire.

Il faut récupérer le contenu de la variable $PATH avec la fonction getenv. Elle prend un seul paramètre qui est la variable que l’on cherche et renvoie un pointeur sur le contenu de la variable passer en paramètre.

Si notre binaire n’est dans aucun dossier, on peut avertir l’utilisateur par un Command not found, sinon on peut exécuter notre execve : D.

La fonction qui récupère le contenue de la variable $PATH et qui renvoie le chemin absolu :

static void	get_absolute_path(char **cmd)
{
	char	*path = strdup(getenv("PATH"));
	char	*bin = NULL;
	char	**path_split = NULL;

	if (path == NULL) // si le path est null, on cree un path
		path = strdup("/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin");

	// si cmd n'est pas le chemin absolue, on cherche le chemin absolue du
	// binaire grace a la variable d'environment PATH
	if (cmd[0][0] != '/' && strncmp(cmd[0], "./", 2) != 0) {

		// On split le path pour verifier ou ce trouve le binaire
		path_split = split(path, ":");
		free(path);
		path = NULL;

		// On boucle sur chaque dossier du path pour trouver l'emplacement du binaire
		for (int i = 0; path_split[i]; i++) {
			// alloc len du path + '/' + len du binaire + 1 pour le '\0'
			bin = (char *)calloc(sizeof(char), (strlen(path_split[i]) + 1 + strlen(cmd[0]) + 1));
			if (bin == NULL)
				break ;

			// On concat le path , le '/' et le nom du binaire
			strcat(bin, path_split[i]);
			strcat(bin, "/");
			strcat(bin, cmd[0]);

			// On verfie l'existence du fichier et on quitte la boucle si access
			// renvoi 0
			if (access(bin, F_OK) == 0)
				break ;

			// Nous sommes des gens propre :D
			free(bin);
			bin = NULL;
		}
		free_array(path_split);

		// On remplace le binaire par le path absolue ou NULL si le binaire
		// n'existe pas
		free(cmd[0]);
		cmd[0] = bin;
	} else {
		free(path);
		path = NULL;
	}
}

Notre main devient :

int	main()
{
	char	*buffer = NULL;
	size_t	buf_size = 2048;

	// alloc buffer qui stockera la commande entree par l'user
	buffer = (char *)calloc(sizeof(char), buf_size);
	if (buffer == NULL) {
		perror("Malloc failure");
		return (EXIT_FAILURE);
	}

	// ecriture d'un prompt
	write(1, "$> ", 3);

	// lecture de STDIN en boucle
	while (getline(&buffer, &buf_size, stdin) > 0) {
		cmd = split(buffer, " \n\t");
		get_absolute_path(cmd);

		if (cmd[0] == NULL)
			printf("Command not found\n");
		else
			exec_cmd(cmd);

		write(1, "$> ", 3);
		free_array(cmd);

	}

	printf("Bye \n");
	free(buffer);
	}

À ce stade, notre shell exécute une commande mais n’a pas de builtin, ni d’environemnt.

On va maintenant ajouter quelques builtin à notre shell.

4) Built-in

Une builtin est une commande coder dans notre shell. c’est-à-dire une commande qui ne va pas s’exécuter avec execve.

Si on lance bash et que l’on supprime la variable d’environment PATH, notre shell doit quand même pouvoir exécuter des commandes rudimentaires.

les commandes cd, pwd, export, echo, exit ... doivent être exécutable.

Vous pouvez lister toutes les builtin sous bash avec la commande help

Nous allons voir les builtin cd, pwd . Il en existe beaucoup d’autres mais le but est juste que vous compreniez qu’est-ce qu’une builtin et comment les implémenter.

Pour la builtin cd, nous allons utiliser la fonction chdir

chdirprend en paramètre le path qui va devenir le dossier courant

Une exemple d’implementaion de la builtin cd :

void	built_in_cd(char *path)
{
	if (chdir(path) == -1) {
		perror("chdir()");
	}
}

Un exemple de la builtin pwd:

void	built_in_pwd(void)
{
	char cwd[PATH_MAX];
	
	if (getcwd(cwd, sizeof(cwd)) != NULL) {
	       printf("%s\n", cwd);
	} else {
		perror("getcwd()");
	}
}

Une fois que nous passerons au code de l’environment, nous modifierons les built-in pour qu’elle utilise l’environment.

Le code à jour avec la gestion des built-in :

// Les deux includes a ajouter pour le type bool et le define PATH_MAX
#include <stdbool.h>
#include <linux/limits.h>

static bool	is_built_in(char *cmd)
{
	const char	*built_in[] = {"pwd", "cd", NULL};

	for (int i = 0; built_in[i]; i++) {
		if (!strcmp(built_in[i], cmd))
			return (true);
	}
	return (false);
}

static void	exec_built_in(char **built_in)
{
	if (!strcmp(built_in[0], "pwd"))
		built_in_pwd();
	else if (!strcmp(built_in[0], "cd"))
		built_in_cd(built_in[1]);
}

int	main()
{
	char	*buffer = NULL;
	size_t	buf_size = 2048;
	char	**cmd = NULL;

	// alloc buffer qui stockera la commande entree par l'user
	buffer = (char *)calloc(sizeof(char), buf_size);
	if (buffer == NULL) {
		perror("Malloc failure");
		return (EXIT_FAILURE);
	}

	// ecriture d'un prompt
	write(1, "$> ", 3);

	// lecture de STDIN en boucle
	while (getline(&buffer, &buf_size, stdin) > 0) {
		cmd = split(buffer, " \n\t");

		if (cmd[0] == NULL)
			printf("Command not found\n");
		else if (is_built_in(cmd[0]) == false) {
			get_absolute_path(cmd);
			exec_cmd(cmd);
		} else
			exec_built_in(cmd);

		write(1, "$> ", 3);
		free_array(cmd);

	}

	printf("Bye \n");
	free(buffer);
}

Nous avons maintenant un shell qui peut exécuter une commande avec execve et des builtin :D.

Nous allons voir maintenant une chose importante dans un shell, l’environment.

5) l’environment

Dans n’importe quel shell Unix, la commande env affiche les variables d’environment.

Une variable d’environment sert à communiquer des informations entre plusieurs programmes.

En C, on peut récupérer l’ensemble des variables d’environment par le 3e argument de la fonction main, char **envp.

On peut soit crée un environment en dupliquant la variable envp, et/ou codant en dur un environment minimaliste.

Nous allons coder une fonction qui stocke les variables d’environment dans une liste chainée.

Si un utilisateur veut ajouter ou supprimer une variable, nous aurons juste à ajouter ou supprimer un maillon de notre liste : D.

Voilà la liste des variables qui seront ajoutées si elle n’existe pas dans envp :

  • PATH : Pour avoir la liste des dossiers ou chercher les binaires a exécuter
  • HOME : Pour connaitre ou est notre home :D
  • OLDPWD : Pour connaitre le dossier dans lequel nous etions
  • PWD : Pour connaitre le path actuelle
  • SHLVL : Pour savoir combien de shell nous avons lancer

On commence par écrire notre fonction qui va dupliquer l’env. On parcourt envp et on stock chaque variable dans une liste chainée.

static void	add_env_var(char *var)
{
	struct passwd	*pw = getpwuid(getuid());
	char			*alloc = NULL;

	if (!strcmp(var, "HOME")) {
		alloc = (char *)calloc(sizeof(char), strlen(pw->pw_dir) + strlen("HOME=") + 1);
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add HOME\n");
			return ;
		}
		strcat(alloc, "HOME=");
		strcat(alloc, pw->pw_dir);
	} else if (!strcmp(var, "PATH")) {
		alloc = strdup("PATH=/bin:/usr/bin");
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add PATH\n");
			return ;
		}
	} else if (!strcmp(var, "OLDPWD")) {
		alloc = strdup("OLDPWD=");
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add OLDPWD\n");
			return ;
		}
	} else if (!strcmp(var, "PWD")) {
		alloc = built_in_pwd();
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add PWD\n");
			return ;
		}
	} else if (!strcmp(var, "SHLVL")) {
		alloc = strdup("SHLVL=1");
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add OLDPWD\n");
			return ;
		}
	}

	add_tail(alloc);
}

static void	dup_env(char **envp)
{
	char	*var_lst[] = {"PATH", "HOME", "OLDPWD", "PWD", "SHLVL", NULL};
	ssize_t	nb_elem = 5; // nombre d'element dasn var_lst

	// boucle sur l'env et stock les variables dans la liste
	for (int i = 0; envp[i]; i++) {
		add_tail(strdup(envp[i]));

		// On verifie que l'on a les variables d'environment minimal
		if (!strncmp(envp[i], "PATH", 4)) var_lst[0] = NULL;
		else if (!strncmp(envp[i], "HOME", 4)) var_lst[1] = NULL;
		else if (!strncmp(envp[i], "OLDPWD", 6)) var_lst[2] = NULL;
		else if (!strncmp(envp[i], "PWD", 3)) var_lst[3] = NULL;
		else if (!strncmp(envp[i], "SHLVL", 5)) var_lst[4] = NULL;
	}

	// On verifie qu l'on a les varaibles PATH, HOME, OLD_PWD et SHLVL
	// sinon on l'ajoute
	for (int i = 0; i < 5; i++) {
		if (var_lst[i] != NULL)
			add_env_var(var_lst[i]);
	}
}

Je ne mets pas les fonctions pour manipuler ma liste chainée, le but de cet article est l’écriture d’un shell. Elles sont dans le code source.

J’ai modifié la builtin pwd. Maintenant qu’on a un env, ont parcour la liste jusqu’à trouver notre variable et on l’affiche.

Au lancement de shell on initialise la variable PWD si elle n’existe pas, et par la suite c’est la buitin cd qui modifiera PWD et OLD_PWD.

static char	*built_in_pwd(void)
{
	char	*cwd = NULL;

	// On alloue la longueur de PWD= + PATH_MAX + 1 pour le \0
	cwd = (char *)calloc(sizeof(char), PATH_MAX + strlen("PWD=") + 1);
	if (cwd == NULL)
		return (NULL);

	// On concatene le nom de la variable
	strcat(cwd, "PWD=");

	// et on stock le path actuelle apres le = de PATH=
	if (getcwd(&cwd[4], PATH_MAX) == NULL) {
		perror("getcwd()");
	}

	return (cwd);
}

J’ai aussi ajouté une builtin env qui affiche tout l’env :

static void	built_in_env(void)
{
	t_env	*tmp = first;

	while (tmp) {
		printf("%s\n", tmp->var);
		tmp = tmp->next;
	}
}

On peut aussi ajouter la builtin setenv pour ajouter une variable et unsetenv pour supprimer une variable. Je ne les ferais pas dans cet article, il suffit juste de supprimer un maillon ou d’en ajouter un à notre liste.

static void	built_in_cd(char *path)
{
	char	*oldpwd = NULL;
	char	*pwd = NULL;
	char	*pwd_ptr = NULL;

	if (path == NULL)
		return;
	if (chdir(path) == 0) {
		pwd = strrchr(get_env_var("PWD="), '=') + 1;
		oldpwd = strrchr(get_env_var("OLDPWD="), '=') + 1;

		if (oldpwd != NULL && pwd != NULL) {
			strcpy(oldpwd, pwd);
		}
		if (pwd != NULL) {
			pwd = &pwd[-strlen("PWD=")];
			pwd_ptr = built_in_pwd();
			strcpy(pwd, pwd_ptr);
			free(pwd_ptr);
			pwd_ptr = NULL;
		}
	} else {
		perror("chdir");
	}
}

Maintenant qu’on a un environment, on peut l’envoyer à execve à la place de NULL :D.

Voilà les bases d’un shell. Il y a énormément de chose qu’on peut ajouter:

  • plein d’autre builtin
  • un cd plus complet (gestion du ~ …)
  • historique
  • prompt dynamique avec le path …
  • etc …

Le code complet en cliquant ici :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdbool.h>
#include <linux/limits.h>
#include <pwd.h>
#include <sys/types.h>

static char	*built_in_pwd(void);

typedef struct		s_env
{
	char			*var;		// var name exemple : PATH
	struct s_env	*next;
}					t_env;

// First elem dans la liste chainee
static t_env	*first = NULL;

static char	*get_env_var(char *var)
{
	t_env	*tmp = first;
	size_t	len = 0;

	len = strlen(var);

	while (tmp) {
		if (!strncmp(var, tmp->var, len)) {
			return (tmp->var);
		}
		tmp = tmp->next;
	}
	return (NULL);
}


static char	**split(char *raw_cmd, char *limit)
{
	char	*ptr = NULL;
	char	**cmd = NULL;
	size_t	idx = 0;

	// split sur les espaces
	ptr = strtok(raw_cmd, limit);

	while (ptr) {
		cmd = (char **)realloc(cmd, ((idx + 1) * sizeof(char *)));
		cmd[idx] = strdup(ptr);
		ptr = strtok(NULL, limit);
		++idx;
	}
	// On alloue un element qu'on met a NULL a la fin du tableau
	cmd = (char **)realloc(cmd, ((idx + 1) * sizeof(char *)));
	cmd[idx] = NULL;
	return (cmd);
}

static void	free_array(char **array)
{
	for (int i = 0; array[i]; i++) {
		free(array[i]);
		array[i] = NULL;
	}
	free(array);
	array = NULL;
}

static void	exec_cmd(char **cmd, char **env)
{
	pid_t	pid = 0;
	int		status = 0;

	// On fork
	pid = fork();
	if (pid == -1)
		perror("fork");
	// Si le fork a reussit, le processus pere attend l'enfant (process fork)
	else if (pid > 0) {
		// On block le processus parent jusqu'a ce que l'enfant termine puis
		// on kill le processus enfant
		waitpid(pid, &status, 0);
		kill(pid, SIGTERM);
	} else {
		// Le processus enfant execute la commande ou exit si execve echoue
		if (execve(cmd[0], cmd, env) == -1)
			perror("shell");
		exit(EXIT_FAILURE);
	}
}

static bool	get_absolute_path(char **cmd, char **env)
{
	char	*path = NULL;
	char	*bin = NULL;
	char	**path_split = NULL;
	size_t	idx = 0;

	// si cmd n'est pas le chemin absolue, on cherche le chemin absolue du
	// binaire grace a la variable d'environment PATH
	if (cmd[0][0] != '/' && strncmp(cmd[0], "./", 2) != 0) {

		// On cherche la variable d'environemnt PATH si on ne la toruve pas,
		// On ne peux pas concatener alors on quitte la fonction
		for (int i = 0; env[i]; i++) {
			if (!strncmp(env[i], "PATH=", 5)) {
				path = strdup(&env[i][5]);
				break ;
			}
		}
		if (path == NULL)
			return (false);

		// On split le path pour verifier ou ce trouve le binaire
		path_split = split(path, ":");
		free(path);
		path = NULL;

		// On boucle sur chaque dossier du path pour trouver l'emplacement du binaire
		for (idx = 0; path_split[idx]; idx++) {
			// alloc len du path + '/' + len du binaire + 1 pour le '\0'
			bin = (char *)calloc(sizeof(char), (strlen(path_split[idx]) + 1 + strlen(cmd[0]) + 1));
			if (bin == NULL)
				break ;

			// On concat le path , le '/' et le nom du binaire
			strcat(bin, path_split[idx]);
			strcat(bin, "/");
			strcat(bin, cmd[0]);

			// On verfie l'existence du fichier et on quitte la boucle si access
			// renvoi 0
			if (access(bin, F_OK) == 0)
				break ;

			// Nous sommes des gens propre :D
			free(bin);
			bin = NULL;
		}
		free_array(path_split);

		// On remplace le binaire par le path absolue ou NULL si le binaire
		// n'existe pas
		free(cmd[0]);
		cmd[0] = bin;
	} else {
		free(path);
		path = NULL;
	}

	// si le binaire existe on renvoie true
	return (bin == NULL ? false : true);
}

static bool	is_built_in(char *cmd)
{
	const char	*built_in[] = {"pwd", "cd", "env", NULL};

	for (int i = 0; built_in[i]; i++) {
		if (!strcmp(built_in[i], cmd))
			return (true);
	}
	return (false);
}

static void	built_in_cd(char *path)
{
	char	*oldpwd = NULL;
	char	*pwd = NULL;
	char	*pwd_ptr = NULL;

	if (path == NULL)
		return;
	if (chdir(path) == 0) {
		pwd = strrchr(get_env_var("PWD="), '=') + 1;
		oldpwd = strrchr(get_env_var("OLDPWD="), '=') + 1;

		if (oldpwd != NULL && pwd != NULL) {
			strcpy(oldpwd, pwd);
		}
		if (pwd != NULL) {
			pwd = &pwd[-strlen("PWD=")];
			pwd_ptr = built_in_pwd();
			strcpy(pwd, pwd_ptr);
			free(pwd_ptr);
			pwd_ptr = NULL;
		}
	} else {
		perror("chdir");
	}
}

static char	*built_in_pwd(void)
{
	char	*cwd = NULL;

	// On alloue la longueur de PWD= + PATH_MAX + 1 pour le \0
	cwd = (char *)calloc(sizeof(char), PATH_MAX + strlen("PWD=") + 1);
	if (cwd == NULL)
		return (NULL);

	// On concatene le nom de la variable
	strcat(cwd, "PWD=");

	// et on stock le path actuelle apres le = de PATH=
	if (getcwd(&cwd[4], PATH_MAX) == NULL) {
		perror("getcwd()");
	}

	return (cwd);
}

static void	built_in_env(void)
{
	t_env	*tmp = first;

	while (tmp) {
		printf("%s\n", tmp->var);
		tmp = tmp->next;
	}
}

static void	exec_built_in(char **built_in)
{
	if (!strcmp(built_in[0], "pwd"))
		printf("%s\n", get_env_var("PWD="));
	else if (!strcmp(built_in[0], "cd"))
		built_in_cd(built_in[1]);
	else if (!strcmp(built_in[0], "env"))
		built_in_env();
}

static void	add_tail(char *var)
{
	t_env	*ptr = first;
	t_env	*new_node = NULL;

	new_node = (t_env *)calloc(sizeof(t_env), 1);
	if (new_node == NULL) {
		printf("Alloc failure\n");
		return ;
	}

	new_node->var = var;
	new_node->next = NULL;

	if (ptr == NULL) {
		first = new_node;
	} else {
		while (ptr->next != NULL)
			ptr = ptr->next;
		ptr->next = new_node;
	}
}

static void	add_env_var(char *var)
{
	struct passwd	*pw = getpwuid(getuid());
	char			*alloc = NULL;

	if (!strcmp(var, "HOME")) {
		alloc = (char *)calloc(sizeof(char), strlen(pw->pw_dir) + strlen("HOME=") + 1);
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add HOME\n");
			return ;
		}
		strcat(alloc, "HOME=");
		strcat(alloc, pw->pw_dir);
	} else if (!strcmp(var, "PATH")) {
		alloc = strdup("PATH=/bin:/usr/bin");
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add PATH\n");
			return ;
		}
	} else if (!strcmp(var, "OLDPWD")) {
		alloc = strdup("OLDPWD=");
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add OLDPWD\n");
			return ;
		}
	} else if (!strcmp(var, "PWD")) {
		alloc = built_in_pwd();
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add PWD\n");
			return ;
		}
	} else if (!strcmp(var, "SHLVL")) {
		alloc = strdup("SHLVL=1");
		if (alloc == NULL) {
			fprintf(stderr, "Cannot add OLDPWD\n");
			return ;
		}
	}

	add_tail(alloc);
}

static void	dup_env(char **envp)
{
	char	*var_lst[] = {"PATH", "HOME", "OLDPWD", "PWD", "SHLVL", NULL};
	ssize_t	nb_elem = 5; // nombre d'element dasn var_lst

	// boucle sur l'env et stock les variables dans la liste
	for (int i = 0; envp[i]; i++) {
		add_tail(strdup(envp[i]));

		// On verifie que l'on a les variables d'environment minimal
		if (!strncmp(envp[i], "PATH", 4)) var_lst[0] = NULL;
		else if (!strncmp(envp[i], "HOME", 4)) var_lst[1] = NULL;
		else if (!strncmp(envp[i], "OLDPWD", 6)) var_lst[2] = NULL;
		else if (!strncmp(envp[i], "PWD", 3)) var_lst[3] = NULL;
		else if (!strncmp(envp[i], "SHLVL", 5)) var_lst[4] = NULL;
	}

	// On verifie qu l'on a les varaibles PATH, HOME, OLD_PWD et SHLVL
	// sinon on l'ajoute
	for (int i = 0; i < nb_elem; i++) {
		if (var_lst[i] != NULL)
			add_env_var(var_lst[i]);
	}
}

static char	**lst_to_array()
{
	char	**array = NULL;
	t_env	*tmp = first;
	size_t	idx = 0;

	// On compte le nombre d'element dans la liste pour allouer un tableau de pointeurs
	// ou chaque pointeur pointera sur notre environment
	while (tmp) {
		idx++;
		tmp= tmp->next;
	}

	// Allcoation (+ 1 pour l'element null de fin)
	array = (char **)calloc(sizeof(char *), idx + 1);
	if (array == NULL) {
		perror("calloc");
		exit(-1);
	}

	// reset des variables
	tmp = first;
	idx = 0;

	// On fait pointer chaque pointeur sur notrte environment
	while (tmp) {
		array[idx] = tmp->var;
		tmp = tmp->next;
		idx++;
	}

	return (array);
}

static void	free_lst(void)
{
	t_env	*idx = first;
	t_env	*tmp = idx;

	while (idx != NULL) {
		tmp = idx;
		idx = idx->next;
		free(tmp->var);
		tmp->var = NULL;
		free(tmp);
		tmp = NULL;
	}
}

int	main(int argc, char **argv, char **envp)
{
	char	*buffer = NULL;
	size_t	buf_size = 2048;
	char	**cmd = NULL;
	char	**env = NULL;

	dup_env(envp);

	// alloc buffer qui stockera la commande entree par l'user
	buffer = (char *)calloc(sizeof(char), buf_size);
	if (buffer == NULL) {
		perror("Malloc failure");
		return (EXIT_FAILURE);
	}

	// ecriture d'un prompt
	write(1, "$> ", 3);

	// lecture de STDIN en boucle
	while (getline(&buffer, &buf_size, stdin) > 0) {
		cmd = split(buffer, " \n\t");

		if (cmd[0] == NULL)
			fprintf(stderr, "Command not found\n");
		else if (is_built_in(cmd[0]) == true) {
			exec_built_in(cmd);
		} else {
			env = lst_to_array();
			if (get_absolute_path(cmd, env) == true) {
				exec_cmd(cmd, env);
			} else {
				fprintf(stderr, "Command not found\n");
			}
			free(env);
			env = NULL;
		}

		write(1, "$> ", 3);
		free_array(cmd);

	}

	free_lst();
	printf("Bye \n");
	free(buffer);

	(void)argc;
	(void)argv;

	return (0);
}