This commit is contained in:
Laureηt 2021-04-20 18:12:44 +02:00
commit ab6da28992
15 changed files with 665 additions and 0 deletions

29
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,29 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "gcc - Build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: gcc build active file",
"miDebuggerPath": "/usr/bin/gdb"
}
]
}

8
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"files.associations": {
"signal.h": "c",
"readcmd.h": "c",
"stdlib.h": "c",
"wait.h": "c"
}
}

BIN
.vscode/tasks vendored Executable file

Binary file not shown.

30
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,30 @@
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: gcc build active file",
"command": "/usr/bin/gcc",
"args": [
"-Wall",
"-g",
"jobs.c",
"readcmd.c",
"minishell.c",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
],
"version": "2.0.0"
}

14
Makefile Normal file
View file

@ -0,0 +1,14 @@
CC = gcc
LDFLAGS = -Wall
all: minishell
minishell: readcmd.c minishell.c
$(CC) $(LDFLAGS) $^ -o $@
clean:
rm *.o minishell
.PHONY: clean all
readcmd.o: readcmd.h

38
README.md Executable file
View file

@ -0,0 +1,38 @@
Introduction
------------
Le code fourni a pour but de vous décharger du travail d'analyse d'une ligne de commande,
avant son interprétation par le shell. Il propose une fonction `readcmd()` qui fournit
le résultat de cette analyse.
Le source est fourni non pas à titre d'exemple (il n'est pas d'une clarté éblouissante),
mais à titre de documentation (et de spécification :)) ; il n'est ni
nécessaire, ni conseillé de le modifier pour réaliser le travail demandé : l'analyse
réalisée est suffisante pour la syntaxe des commandes que votre shell doit interpréter.
Ce code est a priori tout à fait fiable, mais n'hésitez cependant pas à nous signaler
ce qui vous apparaîtrait comme des anomalies, ou des lacunes dans la documentation.
La structure `cmdline`
----------------------
L'appel à readcmd() retourne une structure cmdline, qui contient le résultat de l'analyse
de la ligne de commande pour le shell.
Cette structure contient notamment :
- l'indication du fait que la commande doit être lancée en tâche de fond ou non
- les redirections éventuelles
- la décomposition de la ligne de commande en commandes élémentaires, et la décomposition
de chaque commande en mots. Le champ `seq` référence le résultat de cette décomposition,
qui est vu comme un tableau à deux dimensions [commandes élémentaires, mots de la commande]
**Pour plus de détails, voir le fichier d'en-tête `readcmd.h`.**
**Exemples :**
- "ls -l" : seq[0][0] = "ls", seq[0][1] = "-l", seq[0][2] = NULL, seq[1] = NULL, backgrounded = NULL, in = NULL, out = NULL
- "ls -l > toto" : seq[0][0] = "ls", seq[0][1] = "-l", seq[0][2] = NULL,
seq[1] = NULL, backgrounded = NULL, in = NULL, out => "toto"
- "ls | grep toto | wc -l" : seq[0][0] = "ls", seq[0][1] = NULL,
seq[1][0] = "grep", seq[1][1] = "toto", seq[1][2] = NULL,
seq[2][0] = "wc", seq[0][1] = "-l", seq[0][2] = NULL,
seq[3] = NULL, backgrounded = NULL, in = NULL, out = NULL
- "sleep 100 &" : seq[0][0] = "sleep", seq[0][1] = "20", backgrounded != NULL, in = NULL, out = NULL

BIN
jobs Executable file

Binary file not shown.

85
jobs.c Normal file
View file

@ -0,0 +1,85 @@
#include <stdio.h>
#include <stdlib.h>
#include "jobs.h"
void ajouter(list* l_ptr, int pid, int id) {
cell* new_cell = malloc(sizeof(*new_cell));
new_cell->pid = pid;
new_cell->id = id;
if (*l_ptr == NULL) {
new_cell->next = NULL;
} else {
new_cell->next = *l_ptr;
}
*l_ptr = new_cell;
printf("[%d] %d\n", id, pid);
}
void supprimer(list* l_ptr, int pid) {
cell* cursor = *l_ptr;
if (cursor->pid == pid) {
cell* cursor2free = cursor;
*l_ptr = cursor->next;
free(cursor2free);
} else {
while (cursor->next != NULL) {
if (cursor->next->pid == pid) {
break;
} else {
cursor = cursor->next;
}
}
cell* cursor_next = cursor->next->next;
free(cursor->next);
cursor->next = cursor_next;
}
}
void afficher(list* l_ptr) {
cell* cursor = *l_ptr;
while (cursor != NULL) {
printf("[%d] %d\n", cursor->id, cursor->pid);
cursor = cursor->next;
}
}
void initialiser(list* l_ptr) {
*l_ptr = NULL;
}
void liberer(list* l_ptr) {
free(*l_ptr);
*l_ptr = NULL;
}
int est_vide(list l_ptr) {
return l_ptr == NULL;
}
int contiens(list* l_ptr, int pid) {
cell* cursor = *l_ptr;
while (cursor != NULL) {
if (cursor->pid == pid) {
return 1;
}
cursor = cursor->next;
}
return 0;
}

20
jobs.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef __JOBS_H
#define __JOBS_H
typedef struct cell cell;
struct cell {
int pid;
int id;
cell* next;
};
typedef cell* list;
void ajouter(cell** list, int pid, int id);
void supprimer(cell** list, int pid);
void afficher(cell** list);
void initialiser(list* list);
void liberer(list* list);
int contiens(list* l_ptr, int pid);
#endif

BIN
minishell Executable file

Binary file not shown.

102
minishell.c Normal file
View file

@ -0,0 +1,102 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <setjmp.h>
#include "readcmd.h"
#include "jobs.h"
int prompting = 0;
int job_id = 1;
list jobs;
void handler_print(int signal_num, siginfo_t* info) {
if (contiens(&jobs, info->si_pid)) {
if (prompting) { printf("\n"); }
printf("[%d] --> (%d) %s\n", info->si_pid, signal_num, strsignal(signal_num));
supprimer(&jobs, info->si_pid);
job_id--;
}
}
int main(int argc, char *argv[]) {
extern int errno;
initialiser(&jobs);
struct sigaction action;
action.sa_flags = SA_SIGINFO; //| SA_RESTART;
action.sa_handler = handler_print;
sigemptyset(&action.sa_mask);
sigaction(SIGCHLD, &action, NULL);
char initcd[256], currentcd[256];
getcwd(initcd, 256);
while (1) {
getcwd(currentcd, 256);
printf("%s >>> ", currentcd);
prompting = 1;
struct cmdline* cmd = readcmd();
prompting = 0;
if (cmd == NULL || cmd->seq[0] == NULL) { // ligne vide, on skip
continue;
} else if (!strcmp(cmd->seq[0][0], "exit")) { // on quitte le shell
break;
} else if (!strcmp(cmd->seq[0][0], "cd")) { // cd
if (cmd->seq[0][1] == NULL) { // vide
chdir(initcd);
} else { // avec un path
chdir(cmd->seq[0][1]);
}
continue;
} else if (!strcmp(cmd->seq[0][0], "jobs")) {
afficher(&jobs);
continue;
}
pid_t pidFils = fork();
if (pidFils == -1) {
fprintf(stderr, "ERROR: forking failed, %s\n", strerror(errno));
exit(errno);
}
if (pidFils == 0) { // fils
execvp(cmd->seq[0][0], cmd->seq[0]);
exit(errno); // si execlp échoue on exit avec une erreur
} else { // père
if (cmd->backgrounded) {
ajouter(&jobs, pidFils, job_id++);
} else {
jmp_buf env;
setjmp(env);
int codeTerm;
pid_t idFils = waitpid(pidFils, &codeTerm, 0); // on attend la fin de l'exec du fils
if (idFils == -1){
// todo : peut mieux faire ?
if (errno == 4) {
longjmp(env, 1);
}
fprintf(stderr, "ERROR: waiting for %d failed, (%d) %s\n", codeTerm, errno, strerror(errno));
exit(errno);
}
}
}
}
return EXIT_SUCCESS;
}

BIN
minishell.pdf Normal file

Binary file not shown.

297
readcmd.c Executable file
View file

@ -0,0 +1,297 @@
/*
* Copyright (C) 2002, Simon Nieuviarts
*/
/*
* Backgrounding added. [PM] Ajout d'une rustine nécessaire : lignes 153 et 293 commentées
*/
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include "readcmd.h"
static void memory_error(void)
{
errno = ENOMEM;
perror(0);
exit(1);
}
static void *xmalloc(size_t size)
{
void *p = malloc(size);
if (!p) memory_error();
return p;
}
static void *xrealloc(void *ptr, size_t size)
{
void *p = realloc(ptr, size);
if (!p) memory_error();
return p;
}
/* Read a line from standard input and put it in a char[] */
static char *readline(void)
{
size_t buf_len = 16;
char *buf = xmalloc(buf_len * sizeof(char));
if (fgets(buf, buf_len, stdin) == NULL) {
free(buf);
return NULL;
}
do {
size_t l = strlen(buf);
if ((l > 0) && (buf[l-1] == '\n')) {
l--;
buf[l] = 0;
return buf;
}
if (buf_len >= (INT_MAX / 2)) memory_error();
buf_len *= 2;
buf = xrealloc(buf, buf_len * sizeof(char));
if (fgets(buf + l, buf_len - l, stdin) == NULL) return buf;
} while (1);
}
/* Split the string in words, according to the simple shell grammar. */
static char **split_in_words(char *line)
{
char *cur = line;
char **tab = 0;
size_t l = 0;
char c;
while ((c = *cur) != 0) {
char *w = 0;
char *start;
switch (c) {
case ' ':
case '\t':
/* Ignore any whitespace */
cur++;
break;
case '<':
w = "<";
cur++;
break;
case '>':
w = ">";
cur++;
break;
case '|':
w = "|";
cur++;
break;
case '&':
w = "&";
cur++;
break;
default:
/* Another word */
start = cur;
while (c) {
c = *++cur;
switch (c) {
case 0:
case ' ':
case '\t':
case '<':
case '>':
case '|':
case '&':
c = 0;
break;
default: ;
}
}
w = xmalloc((cur - start + 1) * sizeof(char));
strncpy(w, start, cur - start);
w[cur - start] = 0;
}
if (w) {
tab = xrealloc(tab, (l + 1) * sizeof(char *));
tab[l++] = w;
}
}
tab = xrealloc(tab, (l + 1) * sizeof(char *));
tab[l++] = 0;
return tab;
}
static void freeseq(char ***seq)
{
int i, j;
for (i=0; seq[i]!=0; i++) {
char **cmd = seq[i];
for (j=0; cmd[j]!=0; j++) free(cmd[j]);
free(cmd);
}
free(seq);
}
/* Free the fields of the structure but not the structure itself */
static void freecmd(struct cmdline *s)
{
if (s->in) free(s->in);
if (s->out) free(s->out);
// if (s->backgrounded) free(s->backgrounded);
if (s->seq) freeseq(s->seq);
}
struct cmdline *readcmd(void)
{
static struct cmdline *static_cmdline = 0;
struct cmdline *s = static_cmdline;
char *line;
char **words;
int i;
char *w;
char **cmd;
char ***seq;
size_t cmd_len, seq_len;
line = readline();
if (line == NULL) {
if (s) {
freecmd(s);
free(s);
}
return static_cmdline = 0;
}
cmd = xmalloc(sizeof(char *));
cmd[0] = 0;
cmd_len = 0;
seq = xmalloc(sizeof(char **));
seq[0] = 0;
seq_len = 0;
words = split_in_words(line);
free(line);
if (!s)
static_cmdline = s = xmalloc(sizeof(struct cmdline));
else
freecmd(s);
s->err = 0;
s->in = 0;
s->out = 0;
s->backgrounded = 0;
s->seq = 0;
i = 0;
while ((w = words[i++]) != 0) {
switch (w[0]) {
case '&':
if(s->backgrounded){
s->err = "error on &";
goto error;
}
s->backgrounded = &w[0];
break;
case '<':
/* Tricky : the word can only be "<" */
if (s->in) {
s->err = "only one input file supported";
goto error;
}
if (words[i] == 0) {
s->err = "filename missing for input redirection";
goto error;
}
s->in = words[i++];
break;
case '>':
/* Tricky : the word can only be ">" */
if (s->out) {
s->err = "only one output file supported";
goto error;
}
if (words[i] == 0) {
s->err = "filename missing for output redirection";
goto error;
}
s->out = words[i++];
break;
case '|':
/* Tricky : the word can only be "|" */
if (cmd_len == 0) {
s->err = "misplaced pipe";
goto error;
}
seq = xrealloc(seq, (seq_len + 2) * sizeof(char **));
seq[seq_len++] = cmd;
seq[seq_len] = 0;
cmd = xmalloc(sizeof(char *));
cmd[0] = 0;
cmd_len = 0;
break;
default:
cmd = xrealloc(cmd, (cmd_len + 2) * sizeof(char *));
cmd[cmd_len++] = w;
cmd[cmd_len] = 0;
}
}
if (cmd_len != 0) {
seq = xrealloc(seq, (seq_len + 2) * sizeof(char **));
seq[seq_len++] = cmd;
seq[seq_len] = 0;
} else if (seq_len != 0) {
s->err = "misplaced pipe";
i--;
goto error;
} else
free(cmd);
free(words);
s->seq = seq;
return s;
error:
while ((w = words[i++]) != 0) {
switch (w[0]) {
case '<':
case '>':
case '|':
case '&':
break;
default:
free(w);
}
}
free(words);
freeseq(seq);
for (i=0; cmd[i]!=0; i++) free(cmd[i]);
free(cmd);
if (s->in) {
free(s->in);
s->in = 0;
}
if (s->out) {
free(s->out);
s->out = 0;
}
if (s->backgrounded) {
// free(s->backgrounded);
s->out = 0;
}
return s;
}

40
readcmd.h Executable file
View file

@ -0,0 +1,40 @@
#ifndef __READCMD_H
#define __READCMD_H
/* Lit une ligne de commande depuis l'entrée standard.
* Remarque :
* Dans le cas suffisamment de mémoire ne peut être allouée pour le résultat,
* affiche un message d'erreur et appelle exit().
*/
struct cmdline *readcmd(void);
/* Structure retournée par readcmd()
* Remarques utiles :
* - readcmd() peut retourner null, dans le cas le processus en attente sur readcmd reçoit un signal
* - en cas d'appels successifs,
* readcmd() réutilise la mémoire allouée à la structure cmdline qu'elle retourne
*/
struct cmdline {
char *err; /* Si non null : message d'erreur à afficher.
* Dans ce cas, les autres champs sont nuls. */
char *in; /* Si non null : nom du fichier vers lequel l'entrée doit être redirigée. */
char *out; /* Si non null : nom du fichier vers lequel la sortie doit être redirigée. */
char *backgrounded; /* Si non null : commande en tâche de fond */
char ***seq; /* Une ligne de commande est une suite de commandes liées par des tubes
* Ainsi,
* - une commande est un tableau de chaînes de caractères (char **).
* Chaque élément de ce tableau est en effet une chaîne (char *)
* correspondant à un argument de la commande.
* Le dernier pointeur du tableau est suivi d'un pointeur null, qui
* marque la fin du tableau.
* - seq est un tableau de commandes (char ***), chaque commande de ce
* tableau correspondant à une commande élémentaire du pipeline.
* Le dernier élément (char **) du tableau est ici aussi suivi d'un pointeur null.
* - Dans le cas particulier une ligne vide est saisie, le pointeur référencé par
* seq vaut directement NULL : puisque le tableau n'a aucun élément, le marqueur
* de fin de tableau se trouve référencé par seq, ce que l'on peut écrire
* (*seq == NULL)
* (ou (seq[0] == NULL), mais cela peut prêter à confusion, seq[0] n'existant pas)
*/
};
#endif

2
test.sh Normal file
View file

@ -0,0 +1,2 @@
sleep 5
echo bonjourr