0

So i am implementing a mini C shell, it's supporting background processes. My idea was that for the background mode, the parent process doesn't wait for its children process to finish but rather register them in a job list and when they are done, i capture the SIGCHLD sig to empty their entry on my job list. Here's the code.

#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
#include <wait.h>

#define DEFAULT_PROMPT "\nLog710H2014%>"
#define EXIT_CMD "exit"
#define CD_CMD "cd"
#define JOB_LIST_CMD "aptaches"
#define HOME_ENV_VAR "HOME"
#define NEW_LINE "\n**************************************************\n"
#define BCG_CMD_FLAG "&"

void cd_handler(int argc, char *argv[]);
int lire(char *chaine, int longueur);
char** init_command(int* size,char *str);
int execProg(int *argc, char **argv);
int execProgBg(int *argc, char **argv);
void sigchldHandler(int sig_num);
void aptachesHandler();
void cleanJobList(pid_t *childpid);

struct beanProcess {
    pid_t pid;
    int job_num;
    char *command;
};
void ajoutProcess(struct beanProcess bp);


struct beanProcess beans[20];
int jobCount = 1;

int main() {
    signal(SIGCHLD, sigchldHandler);

    printf(NEW_LINE);
    printf("Bienvenue sur le shell de l'equipe 1");
    printf(NEW_LINE);

    while(1){
        char str[200]="";
        printf(DEFAULT_PROMPT);

        lire(str, 200);

        int commArgsC = 0, bg = 0;
        char** comms  = init_command(&commArgsC, str);

        if(commArgsC == 0){
            //printf("Saisie vide, veuillez entrez une commande.");
            continue;
        }

        if(strcmp(comms[commArgsC-1], BCG_CMD_FLAG) == 0){
            bg = 1;
            comms[commArgsC-1] = 0;
        }

        if(strcmp(comms[0], CD_CMD) == 0){
            cd_handler(commArgsC, comms);
            commArgsC = commArgsC -1;
        }

        else if (strcmp(comms[0], JOB_LIST_CMD) == 0){
            aptachesHandler();
        }

        else if (strcmp(comms[0], EXIT_CMD) == 0){
            int beansVide = 1;
            for(int i = 0; i < jobCount -1 ; i++){
                if(beans[i].pid != 0){
                    beansVide = 0;
                }
            }
            if(beansVide){
                exit(0);
            }else{
                printf("\nImpossible d'arreter le programme, des processus sont encore en cours d'éxécution\n");
            }
        }
        else {
            if(bg){
                execProgBg(&commArgsC, comms);
            }
            else{
                execProg(&commArgsC, comms);
            }
        }
    }
    return 0;
}
void cd_handler(int argc, char *argv[]){
    char buff[512];
    char * directory;

    if(argc < 2){
        directory  = getenv(HOME_ENV_VAR);
    }else if (argc == 2){
        directory = argv[1];
    }else{
        exit(1);
    }

    if (chdir(directory) == -1) {
        printf ("Erreur de changement de repertoire actif", strerror (errno));
    }else{
        if (getcwd(buff, sizeof(buff)) == NULL)
            perror("Impossible d'afficher le repertoire courant");
        else
            printf("le repertoire courant est: %s\n", buff);
    }
}
//Cette fonction est adaptée a partir du code de refp sur http://stackoverflow.com/questions/11198604/c-split-string-into-an-array-of-strings
char** init_command(int* size, char* str){
    char ** res  = NULL;
    char *  p    = strtok (str, " ");
    int n_spaces = 0;

    while (p) {
        res = realloc (res, sizeof (char*) * ++n_spaces);

        if (res == NULL){
            exit (-1);
        }
        res[n_spaces-1] = p;
        p = strtok (NULL, " ");
    }
    res = realloc (res, sizeof (char*) * (n_spaces+1));
    res[n_spaces] = 0;
    *size = n_spaces;
    return res;
}
//cette fonction est tirée d'un exemple de http://fr.openclassrooms.com/informatique/cours/apprenez-a-programmer-en-c/recuperer-une-chaine-de-caracteres
int lire(char *chaine, int longueur)
{
    char *positionEntree = NULL;
    //printf ("\nje suis avant fgets et char est %s", chaine);
    if (fgets(chaine, longueur, stdin) != NULL)
    {
        //printf ("\nje suis apres fgets");
        positionEntree = strchr(chaine, '\n');
        if (positionEntree != NULL)
        {
            *positionEntree = '\0';
        }
        return 1;
    }
    else
    {
        return 0;
    }
}
int execProg(int *argc, char **argv){
    char path[30] = "/bin/";
    strcat(path,argv[0]);
    //printf("\nThis is the %d process executing the code in non bg mode\n", getpid());
    printf("Voici le resultat de l'execution de votre commande\n");
    pid_t  pid;
    pid = fork();

    if (pid < 0) {
        perror("Creation de processus avec fork echouee");
        exit(-1);
    }
    else if (pid == 0) {
        if(execvp(argv[0], argv) == -1){
            //printf("\nthis is the child process %d executing the command in non bg mode\n", getpid());
            perror("execv");
            return EXIT_FAILURE;
        }
    }
    else {
        //printf("\nthis is the parent process %d showing the stats in non bg mode\n", getpid());
        struct rusage rusg;
        long temp, tempCpu;
        wait (NULL);
        getrusage(RUSAGE_CHILDREN, &rusg);
        printf("\nStatistique de la commande %s:\n", argv[0]);

        temp = (rusg.ru_utime.tv_sec * 1000) + (rusg.ru_utime.tv_usec / 1000);
        tempCpu = (rusg.ru_stime.tv_sec * 1000) + (rusg.ru_stime.tv_usec / 1000);

        printf("\nLe temps wall-clock (ms): %ld", temp);
        printf("\nLe temps CPU (ms) %ld", tempCpu);
        printf("\nNB interruptions volontaires: %ld", rusg.ru_nvcsw);
        printf("\nNB interruptions involontaires: %ld", rusg.ru_nivcsw);
        printf("\nNB defaults de pages: %ld", rusg.ru_majflt);
        printf("\nNB defaults de pages satifaits du noyau : %ld", rusg.ru_minflt);
    }
    return EXIT_SUCCESS;
}
int execProgBg(int *argc, char **argv){

    //printf("\nThis is the %d process executing the code in  bg mode\n", getpid());

    pid_t  pid;
    pid = fork();

    if (pid < 0) {
        perror("Creation de processus avec fork echouee");
        return EXIT_FAILURE;
    }
    else if (pid == 0) {
        //printf("This is the pid %d", getpid());
        //printf("\nthis is the child process %d executing the command in  bg mode\n", getpid());

        if(execvp(argv[0], argv) == -1){
            perror("execvp");
            return EXIT_FAILURE;
        }
    }
    else {
        //printf("\nthis is the parent process %d showing the queue in bg mode\n", getpid());

        printf("[%d] %d", jobCount, pid);

        struct beanProcess bP;
        bP.pid = pid;
        bP.job_num = jobCount;
        bP.command = argv[0];

        ajoutProcess(bP);
    }
    return EXIT_SUCCESS;
}
void sigchldHandler(int sig_num)
{
    int status;
    pid_t childPid;
    childPid = waitpid(-1, &status, WNOHANG);
    cleanJobList(&childPid);
}
void ajoutProcess(struct beanProcess bP){
    beans[jobCount-1] = bP;
    jobCount++;
}
void aptachesHandler(){
    for(int i = 0; i < jobCount-1 ; i++){
        printf("[%d] %d %s\n", beans[i].job_num, beans[i].pid, beans[i].command) ;

    }
}
void cleanJobList(pid_t *childpid){
    printf("clean performed on %d", *childpid);
    for(int i = 0; i < jobCount-1 ; i++){
        if(beans[i].pid == *childpid){
            beans[i].pid = 0;
            beans[i].job_num = 0;
            beans[i].command = NULL;
        }
    }
}

With this code i have two problems, first the sigchldHandler works and clean the list only if the BG command (let's say "ls &") is the very first one that was executed. second problem is that with my aptaches command (equivalent to shell jobs) the string of the command name for all processes always takes the last command value, why is that ? Here is a sample of execution so that you see what i am talking about.

**************************************************
Bienvenue sur le shell de l'equipe 1
**************************************************

Log710H2014%>ls &
[1] 10466
Log710H2014%>Debug  PARTIE3.c
clean performed on 10466
Log710H2014%>pwd &
[2] 10467
Log710H2014%>/home/shong/workspace/TP1_PARTIE_3


Log710H2014%>aptaches
[0] 0 (null)
[2] 10467 aptaches

Log710H2014%>

and another one:

**************************************************
Bienvenue sur le shell de l'equipe 1
**************************************************

Log710H2014%>ls
Voici le resultat de l'execution de votre commande
Debug  PARTIE3.c
clean performed on -1
Statistique de la commande ls:

Le temps wall-clock (ms): 1
Le temps CPU (ms) 0
NB interruptions volontaires: 1
NB interruptions involontaires: 3
NB defaults de pages: 0
NB defaults de pages satifaits du noyau : 315
Log710H2014%> ls &
[1] 10483
Log710H2014%>Debug  PARTIE3.c


Log710H2014%>pwd &
[2] 10484
Log710H2014%>/home/shong/workspace/TP1_PARTIE_3


Log710H2014%>aptaches
[1] 10483 ptaches
[2] 10484 aptaches

Log710H2014%>
user2966439
  • 307
  • 1
  • 5
  • 14
  • TL;DR! If you haven't done so yet, please read [the help pages](http://stackoverflow.com/help), especially the sections named ["What topics can I ask about here?"](http://stackoverflow.com/help/on-topic) and ["What types of questions should I avoid asking?"](http://stackoverflow.com/help/dont-ask). And more importantly, please read [the Stack Overflow question checklist](http://meta.stackexchange.com/questions/156810/stack-overflow-question-checklist). You might also want to learn what a [SSCCE](http://sscce.org/) is. – Some programmer dude Feb 11 '14 at 17:20
  • Everything here is relevant since i don't have any clue where the problem is coming from – user2966439 Feb 11 '14 at 17:23
  • `i don't have any clue where the problem is coming from` StackOverflow might not be the best place for you to start, then. Questions here should be specific, and about issues for which you've spent time troubleshooting and are eventually stuck in. – admdrew Feb 11 '14 at 17:24
  • That'S what i did, but how can you debug a signal trapping issue ? – user2966439 Feb 11 '14 at 17:25
  • Debug it further then, either by stepping through the code in a debugger, or by adding `printf` calls at a few places until you narrow it down. – Some programmer dude Feb 11 '14 at 17:25
  • Did that, i have put flags everywhere but the signal_hander is asynchrounous, it's simply not called. – user2966439 Feb 11 '14 at 17:27

1 Answers1

1

Running a command in the foreground or in the background isn't that different -- in fact, the only difference is that in one case you wait for the program to exit before redisplaying the prompt.

Child programs can terminate in any order, e.g.:

sleep 1 &
sleep 2

With your code, when you execute the "sleep 2", the wait(NULL) call will actually pick up the "sleep 1" exiting, and throw away that information, leaving nothing to do for the signal handler.

Also, the signal(2) manpage states that signal handlers are automatically reset when the signal is received, unless BSD semantics for signals are used. You may want to read the "Portability" section of that manual page.

The issue with the command names is simply that you are reusing the buffer where the command name was stored in. You may want to use strdup to make a copy of the command name, and free to reclaim the memory for the copy when the job exits.

Simon Richter
  • 28,572
  • 1
  • 42
  • 64
  • but i don't launch sleep 2 until sleep 1 is finished, when i type "ls &" This is a command that takes nothing to be completed. so i launch a second command like "pwd &" i know that the first one finished – user2966439 Feb 11 '14 at 17:39
  • Yes, there is also the issue of signal handlers being automatically reset, depending on which system you are running on. Updated my answer. – Simon Richter Feb 11 '14 at 17:48
  • Thanks Simon, i moved signal in the while loop and it's working now, so it was ideed reset after the first trap. – user2966439 Feb 11 '14 at 17:52
  • The normal approach is to invoke `signal` again at the end of the signal handler. – Simon Richter Feb 11 '14 at 19:49