Commit d9491137 authored by Jules Sang's avatar Jules Sang
Browse files

Merge branch 'question_4' into 'master'

Question 4

See merge request !1
parents 6d17918e 37ee7fb4
cmake_minimum_required(VERSION 2.6)
if(COMMAND cmake_policy)
cmake_policy(SET CMP0003 NEW)
endif(COMMAND cmake_policy)
project(Ensishell)
enable_testing()
#######
# Detect presence of minitest using gem
# install it if not available
# CentOS7 use ruby 2.0 (2013), minitest > 5.11.3 asks 2.2.
# try to install 5.11.3 version of minitest if standard version failed
#######
execute_process(COMMAND gem which minitest RESULT_VARIABLE GEM_MINITEST)
if (NOT ${GEM_MINITEST} EQUAL 0)
execute_process(COMMAND gem install minitest RESULT_VARIABLE GEM_MINITEST_INSTALL)
if (NOT ${GEM_MINITEST_INSTALL} EQUAL 0)
execute_process(COMMAND gem install minitest --version 5.11.3 RESULT_VARIABLE GEM_MINITEST_INSTALL_OLD)
if (NOT ${GEM_MINITEST_INSTALL_OLD} EQUAL 0)
message(FATAL_ERROR "Ruby: gem: minitest unavailable and uninstallable")
endif ()
endif ()
endif ()
#######
# Detect Guile en utilisant pkg-config
# Si guile est desactivé dans ensishell.c vous pouvez commenter les lignes
#####
# Guile 2.0 by default
execute_process(COMMAND pkg-config --cflags guile-2.0 OUTPUT_VARIABLE GUILE_PKG_CFLAGS)
execute_process(COMMAND pkg-config --libs guile-2.0 OUTPUT_VARIABLE GUILE_PKG_LDFLAGS)
# Guile 1.8 if guile 2.0 is not available
if ("${GUILE_PKG_LDFLAGS}" STREQUAL "")
execute_process(COMMAND pkg-config --cflags guile-1.8 OUTPUT_VARIABLE GUILE_PKG_CFLAGS)
execute_process(COMMAND pkg-config --libs guile-1.8 OUTPUT_VARIABLE GUILE_PKG_LDFLAGS)
endif()
# aucun Guile n'a été trouvé
if (NOT "${GUILE_PKG_LDFLAGS}" STREQUAL "")
set(USE_GUILE 1)
string(STRIP ${GUILE_PKG_CFLAGS} GUILE_CFLAGS)
string(STRIP ${GUILE_PKG_LDFLAGS} GUILE_LDFLAGS)
# Ensimag debug: if libltld.so is required but not there, put explicit path
find_library(LIBLTDL ltdl)
if (${GUILE_LDFLAGS} MATCHES "-lltdl" AND NOT ${LIBLTDL})
string(REPLACE "-lltdl" "/usr/lib64/libltdl.so.7" GUILE_LDFLAGS ${GUILE_LDFLAGS})
endif()
else()
set(USE_GUILE 0)
endif()
####
# Detect if gnu readline header is present, otherwise use internal readline
####
find_path(READLINE_PATH "include/readline/readline.h")
if (NOT "${READLINE_PATH}" STREQUAL "")
set(USE_GNU_READLINE 1)
set(READLINE_LDFLAGS readline history)
else()
set(USE_GNU_READLINE 0)
set(READLINE_LDFLAGS)
endif()
# Debug build
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${GUILE_CFLAGS} -Wall -Werror -std=gnu99")
#########
# Gestion des variantes
#########
# Vous devez editer les lignes suivantes pour y mettre vos logins
# et le numéro de la variante que vous avez choisi
#########
set(VARIANTE_LOGINS plichonl sangj)
set(VARIANTE_SUJET 0)
###
list(SORT VARIANTE_LOGINS)
if (VARIANTE_LOGINS MATCHES "login[123]")
message(FATAL_ERROR "** ERREUR **: Vous devez modifier CMakeLists.txt pour y mettre vos logins")
endif()
if (VARIANTE_SUJET EQUAL -1)
message(FATAL_ERROR "** ERREUR **: Vous devez modifier CMakeLists.txt pour y mettre le numéro de votre variante du sujet")
endif()
configure_file (
src/variante.h.in
${CMAKE_SOURCE_DIR}/src/variante.h
)
#########
# Fin de gestion des variantes
#########
##
# Si vous utilisez plusieurs fichiers, en plus de ensishell.c, pour votre
# shell il faut les ajouter ici
##
add_executable(ensishell src/readcmd.c src/ensishell.c)
target_link_libraries(ensishell ${READLINE_LDFLAGS} ${GUILE_LDFLAGS})
##
# Programme de test
##
add_test(UnitShellTests ../tests/allShellTests.rb)
##
# Ajout d'une cible pour lancer les tests de manière verbeuse
##
add_custom_target(check ../tests/allShellTests.rb)
##
# Construction de l'archive
##
string(REPLACE ";" "-" LOGINS_SANS_POINTVIRGULE "${VARIANTE_LOGINS}")
set(CPACK_PACKAGE_VERSION_MAJOR "1")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH ${LOGINS_SANS_POINTVIRGULE})
set(CPACK_SOURCE_GENERATOR "TGZ")
set(CPACK_SOURCE_IGNORE_FILES
"~$"
"\\\\.o$"
"^${PROJECT_SOURCE_DIR}/build/"
"^${PROJECT_SOURCE_DIR}/.git/"
)
include(CPack)
This diff is collapsed.
ensimag-shell
=============
Squelette pour le TP shell
Tous les fichiers sont sous licence GPLv3+
Introduction
----------
Ces fichiers servent de base à un TP sur le shell de commande à la Unix.
Spécificités de ce squelette:
- plusieurs variantes (libre choix par les étudiants)
- jeux de tests fournis aux étudiants
- utilisation de Gnu Readline
- Scheme (interpréteur Guile; Javascript possible)
Compilation et lancement des tests
----------
cd ensimag-shell
cd build
cmake ..
make
make test
Autres
------
Les premières versions imposaient la variante par un modulo sur le
sha512 sur de la liste triée des logins des étudiants. Cela peut être
réalisé complètement en CMake (>= 2.8).
/*****************************************************
* Copyright Grégory Mounié 2008-2015 *
* Simon Nieuviarts 2002-2009 *
* This code is distributed under the GLPv3 licence. *
* Ce code est distribué sous la licence GPLv3+. *
*****************************************************/
#include "ensishell.h"
#ifndef VARIANTE
#error "Variante non défini !!"
#endif
/* Guile (1.8 and 2.0) is auto-detected by cmake */
/* To disable Scheme interpreter (Guile support), comment the
* following lines. You may also have to comment related pkg-config
* lines in CMakeLists.txt.
*/
#if USE_GUILE == 1
int question6_executer(char *line)
{
/* Question 6: Insert your code to execute the command line
* identically to the standard execution scheme:
* parsecmd, then fork+execvp, for a single command.
* pipe and i/o redirection are not required.
*/
printf("Not implemented yet: can not execute %s\n", line);
/* Remove this line when using parsecmd as it will free it */
free(line);
return 0;
}
SCM executer_wrapper(SCM x)
{
return scm_from_int(question6_executer(scm_to_locale_stringn(x, 0)));
}
#endif
void terminate(char *line) {
#if USE_GNU_READLINE == 1
/* rl_clear_history() does not exist yet in centOS 6 */
clear_history();
#endif
if (line)
free(line);
printf("exit\n");
exit(0);
}
int main() {
printf("Variante %d: %s\n", VARIANTE, VARIANTE_STRING);
#if USE_GUILE == 1
scm_init_guile();
/* register "executer" function in scheme */
scm_c_define_gsubr("executer", 1, 0, 0, executer_wrapper);
#endif
struct process_cell* processes_list = NULL;
while (1) {
struct cmdline *l;
char *line=0;
int i, j;
char *prompt = "ensishell>";
/* Readline use some internal memory structure that
can not be cleaned at the end of the program. Thus
one memory leak per command seems unavoidable yet */
line = readline(prompt);
if (line == 0 || ! strncmp(line,"exit", 4)) {
terminate(line);
}
#if USE_GNU_READLINE == 1
add_history(line);
#endif
#if USE_GUILE == 1
/* The line is a scheme command */
if (line[0] == '(') {
char catchligne[strlen(line) + 256];
sprintf(catchligne, "(catch #t (lambda () %s) (lambda (key . parameters) (display \"mauvaise expression/bug en scheme\n\")))", line);
scm_eval_string(scm_from_locale_string(catchligne));
free(line);
continue;
}
#endif
/* parsecmd free line and set it up to 0 */
l = parsecmd( & line);
/* If input stream closed, normal termination */
if (!l) {
terminate(0);
}
if (l->err) {
/* Syntax error, read another command */
printf("error: %s\n", l->err);
continue;
}
if (l->in) printf("in: %s\n", l->in);
if (l->out) printf("out: %s\n", l->out);
if (l->bg) printf("background (&)\n");
/* Display each command of the pipe */
for (i=0; l->seq[i]!=0; i++) {
char **cmd = l->seq[i];
printf("seq[%d]: ", i);
for (j=0; cmd[j]!=0; j++) {
printf("'%s' ", cmd[j]);
}
printf("\n");
}
pid_t pid;
// test if l->seq[0] is null in case of line return alone
if (l->seq[0] && strcmp(l->seq[0][0],"jobs") == 0){
// jobs code
// update processes' list
struct process_cell* read_cell = processes_list;
if(read_cell) {
int status;
pid_t res = waitpid(read_cell->pid, &status, WNOHANG);
if(res != 0) {
// process ended, remove from list
// free(read_cell->command);
processes_list = read_cell->next;
free(read_cell->command);
free(read_cell);
read_cell = processes_list;
}
}
while (read_cell && read_cell->next) {
int status;
process_cell* next = read_cell->next;
pid_t res = waitpid(next->pid, &status, WNOHANG);
if(res != 0) {
// process ended, remove from list
// free(next->command);
read_cell->next = next->next;
free(next->command);
free(next);
}
read_cell = read_cell->next;
}
//print processes' list
printf("%20s %50s\n", "PID", "Command");
read_cell = processes_list;
while (read_cell) {
// int terminated = WIFEXITED(status);
// printf("command: %s, PID : %i, terminated: %d, status: %d, waitpid: %d, exit status: %d\n", read_cell->command, read_cell->pid, terminated, status, res, WEXITSTATUS(status));
printf("%20d %50s\n", read_cell->pid, read_cell->command);
read_cell = read_cell->next;
}
printf("\n");
} else {
switch(pid = fork()) {
case -1:
perror("fork:");
break;
case 0:
// son
execvp(l->seq[0][0], l->seq[0]);
break;
default:
//father
if(!l->bg){
waitpid(pid, NULL, 0);
} else {
// background task --> add it to the list of background tasks
process_cell* current_cell = malloc(sizeof(struct process_cell));
current_cell->next = NULL;
current_cell->command = malloc(strlen(l->seq[0][0]) + 1);
current_cell->pid = pid;
strcpy(current_cell->command, l->seq[0][0]);
current_cell->next = processes_list;
processes_list = current_cell;
}
}
}
}
}
#ifndef ENSISHELL_H
#define ENSISHELL_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libguile.h>
#include "variante.h"
#include "readcmd.h"
struct process_cell {
struct process_cell* next;
char* command;
pid_t pid;
};
typedef struct process_cell process_cell;
#endif
\ No newline at end of file
/*****************************************************
* Copyright Grégory Mounié 2008-2013 *
* Matthieu Moy 2008 *
* Simon Nieuviarts 2002-2009 *
* This code is distributed under the GLPv3 licence. *
* Ce code est distribué sous la licence GPLv3+. *
*****************************************************/
#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;
}
#if USE_GNU_READLINE == 0
/* Read a line from standard input and put it in a char[] */
char *readline(char *prompt)
{
size_t buf_len = 16;
char *buf = xmalloc(buf_len * sizeof(char));
printf(prompt);
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);
}
#endif
#define READ_CHAR *(*cur_buf)++ = *(*cur)++
#define SKIP_CHAR (*cur)++
static void read_single_quote(char ** cur, char ** cur_buf) {
SKIP_CHAR;
while(1) {
char c = **cur;
switch(c) {
case '\'':
SKIP_CHAR;
return;
case '\0':
fprintf(stderr, "Missing closing '\n");
return;
default:
READ_CHAR;
break;
}
}
}
static void read_double_quote(char ** cur, char ** cur_buf) {
SKIP_CHAR;
while(1) {
char c = **cur;
switch(c) {
case '"':
SKIP_CHAR;
return;
case '\\':
SKIP_CHAR;
READ_CHAR;
break;
case '\0':
fprintf(stderr, "Missing closing \"\n");
return;
default:
READ_CHAR;
break;
}
}
}
static void read_word(char ** cur, char ** cur_buf) {
while(1) {
char c = **cur;
switch (c) {
case '\0':
case ' ':
case '\t':
case '<':
case '>':
case '|':
case '&':
**cur_buf = '\0';
return;
case '\'':
read_single_quote(cur, cur_buf);
break;
case '"':
read_double_quote(cur, cur_buf);
break;
case '\\':
SKIP_CHAR;
READ_CHAR;
break;
default:
READ_CHAR;
break;
}
}
}
/* Split the string in words, according to the simple shell grammar. */
static char **split_in_words(char *line)
{
char *cur = line;
char *buf = malloc(strlen(line) + 1);
char *cur_buf;
char **tab = 0;
size_t l = 0;
char c;
while ((c = *cur) != 0) {
char *w = 0;
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 */
cur_buf = buf;
read_word(&cur, &cur_buf);
w = strdup(buf);
}
if (w) {
tab = xrealloc(tab, (l + 1) * sizeof(char *));
tab[l++] = w;
}
}
tab = xrealloc(tab, (l + 1) * sizeof(char *));
tab[l++] = 0;
free(buf);
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->seq) freeseq(s->seq);
}
struct cmdline *parsecmd(char **pline)
{
char *line = *pline;
static struct cmdline *static_cmdline = 0;
struct cmdline *s = static_cmdline;
char **words;
int i;
char *w;
char **cmd;
char ***seq;
size_t cmd_len, seq_len;
if (line == NULL) {
if (s) {
freecmd(s);
free(s);
}