Question sur `exit`

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous,

Je dois re-développer la fonction C system : pour ce faire, j'utilise l'appel-système fork qui permet de créer un processus enfant et c'est ce dernier qui doit exécuter la commande passée en paramètre à system, en appelant exec.

Ce que j'ai fait semble bien marcher (pas d'erreur de compilation ni d'exécution).

Le seul souci se situe au niveau du retour de ma méthode system (appelée mySystem dans mon code). Par exemple, dans mon processus enfant, quand je donne un shell inexistant à exec et donc que cette dernière retourne -1, mon processus enfant s'arrête avec un exit code de -1 car c'est ce que je lui dis de faire. Le souci c'est que mon processus parent, qui récupère cet exit code grâce à un wait(&status), retourne… 255 et non -1.

Je ne comprends pas du tout pourquoi. J'ai bien fait attention à utiliser la macro WEXISTSTATUS dans le retour de mon mySystem.

Donc voilà, est-ce que quelqu'un aurait une idée de ce qui ne va pas s'il vous plaît ?

Merci d'avance ! :)

Source

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

pid_t pid;

int mySystem(char*);
int main(int argc, char* argv[]) {

    int result = mySystem("ls");
    fprintf(stdout, "%i", result);

    return 0;
}

int mySystem(char* command) {
    pid = fork();
    if(pid == -1) {
        perror("Fork");
        return -1; // An error occurred => return -1
    } else if (pid == 0) { // The child process will do the following
        execl("BLABLABLABLA MAUVAIS SHELL BLABLABLAA", "sh", "-c", command, NULL); // If this call doesn't fail, the following lines are not read
        perror("Exec"); // If (and only if) execl couldn't be called (bad shell's path, etc.)...
        exit(-1); // ..., we stop the child process and this one has an exit code equaled to -1
    }

    /*
     * NOW, the child process ended because... :
     * 1. Either because of our "exit(-1)" after the "perror"               (our source-code)
     * 2. OR because of an "exit(-1") of the command passed into the execl  (source-code of the execl's command)
     * 3. OR because of the "exit(0)" of the command passed into the execl  (source-code of the execl's command)
     */

    // The parent process will execute the following lines (child process ended)
    int status = -1;
    if(wait(&status) == -1) { // We store into the var 'status' the exit code of the child process : -1 or 0
        perror("Wait"); // Note that because we have only one process child, we don't need to do : while(wait(&status) > 0) {;;}
        return -1;
    }

    return WEXITSTATUS(status); // Our function mySystem returns this exit code
}

Edit : Topic résolu

Voici le code final (cas où command vaut NULL non-traité) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

pid_t pid;

int mySystem(const char*);
int main(int argc, char* argv[]) {

    int result = mySystem("ls");
    fprintf(stdout, "%i", result);

    return 0;
}

int mySystem(const char* command) {
    pid = fork();
    if(pid == -1) {
        perror("Fork");
        return -1; // An error occurred => return -1
    } else if (pid == 0) { // The child process will do the following
        execl("/bin/sh", "sh", "-c", command, NULL); // If this call doesn't fail, the following lines are not read
        perror("Exec"); // If (and only if) execl couldn't be called (bad shell's path, etc.)...
        exit(127); // ..., we stop the child process and this one has an exit code equaled to -1
    }

    /*
     * NOW, the child process ended because... :
     * 1. Either because of our "exit(-1)" after the "perror"               (our source-code)
     * 2. OR because of an "exit(-1") of the command passed into the execl  (source-code of the execl's command)
     * 3. OR because of the "exit(0)" of the command passed into the execl  (source-code of the execl's command)
     */

    // The parent process will execute the following lines (child process ended)
    int status = -1;
    if(wait(&status) == -1) { // We store into the var 'status' the exit code of the child process : -1 or 0
        perror("Wait"); // Note that because we have only one process child, we don't need to do : while(wait(&status) > 0) {;;}
        return -1;
    }

    if(WIFEXITED(status)) {
        return (signed char) WEXITSTATUS(status); // Our function mySystem returns this exit code
    } else {
        return -1;
    }

}
+0 -0

Bonjour entwanne,

Un code de retour négatif traduit la présence d'une erreur, par ailleurs errno est valuée et on peut afficher le message d'erreur grâce à perror. Ainsi, avec le code que j'ai donné, le perror("Exec"); est lu et dit bien que le shell est introuvable.

Non impossible de retourner 1 en code d'erreur :o

Le man nous dit :

WEXITSTATUS(status)

returns the exit status of the child. This consists of the least significant 8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or as the argument for a return statement in main(). This macro should be employed only if WIFEXITED returned true.

Je ne suis pas certain à 100% de ma réponse, mais il me semble que ton 255 est simplement un -1 casté en unsigned char. Je te suggère de quitter tes processus fils avec des valeurs positives en cas d'erreur, c'est-à-dire de façon standard.

+0 -0

Ah je viens de remplacer -1 par des valeurs positives et celles-ci sont bien affichées. Vous avez tous les deux parfaitement raison.

Cependant, ne pourrais-je pas tenter un cast pour retourner une valeur négative ? Je dois vraiment recoder la fonction C officielle system, ce qui inclut de retourner la même chose que cette dernière.

Cependant, ne pourrais-je pas tenter un cast pour retourner une valeur négative ? Je dois vraiment recoder la fonction C officielle system, ce qui inclut de retourner la même chose que cette dernière.

Lern-X

Oui, mais as-tu lu le manuel de system pour voir quelle valeur devrait être retournée dans ce cas ? Chez moi ça parle plutôt de 127 que de -1. Et en règle générale, il est étrange d'utiliser un code de retour non compris entre 0 et 127.

Ah génial !

Et est-ce que vous avez lu mes commentaires ? J'aimerais m'assurer que j'ai bien compris le fonctionnement des processus. Par exemple, dans la plupart des codes, on a la présence d'un else dans : if(fork() == 0) else et moi non. Grâce au fait, et je voudrais en être sûr, que mon processus enfant est forcément tué suite soit à la commande passée au exec soit à mon exit(-1).

C'est bien ça ou je me trompe ?

(d'ailleurs j'y pense : si la commande passée au exec ne tombe par sur un exit mais juste sur le return (donc pas d'erreur), est-ce que mon processus enfant va être bien tué ?)

EDIT pour @entwanne :

Je crois bien que toutes les fonctions C que j'ai utilisées jusque-là retournaient uniquement -1 comme code d'erreur et rien d'autre (pour system : "The value returned is -1 on error (e.g., fork(2) failed), and the return status of the command otherwise." http://linux.die.net/man/3/system)

+0 -0

Je crois bien que toutes les fonctions C que j'ai utilisées jusque-là retournaient uniquement -1 comme code d'erreur et rien d'autre (pour system : "The value returned is -1 on error (e.g., fork(2) failed), and the return status of the command otherwise." http://linux.die.net/man/3/system)

Lern-X

Je parle du code de retour du programme, pas de system. Tu ne devrais pas avoir de exit(-1), ça n'a pas vraiment de sens. (et tu omets de citer la partie intéressante du manuel quant au code de retour)

d'ailleurs j'y pense : si la commande passée au exec ne tombe par sur un exit mais juste sur le return (donc pas d'erreur), est-ce que mon processus enfant va être bien tué ?

Lern-X

Je n'ai pas compris ce que tu voulais dire. Tu parles d'un return qui serait dans le main du programme appelé via exec dans le processus fils ? Alors oui, ça aura le même effet qu'exit.

Je crois bien que toutes les fonctions C que j'ai utilisées jusque-là retournaient uniquement -1 comme code d'erreur et rien d'autre (pour system : "The value returned is -1 on error (e.g., fork(2) failed), and the return status of the command otherwise." http://linux.die.net/man/3/system)

Lern-X

Je parle du code de retour du programme, pas de system. Tu ne devrais pas avoir de exit(-1), ça n'a pas vraiment de sens. (et tu omets de citer la partie intéressante du manuel quant au code de retour)

Du programme passé à exec ou de mon mySystem ? On s'y perd =/

Concernant exit(-1), je t'avoue ne pas comprendre en quoi il n'a pas de sens. En effet, si la commande exec exécutée par le processus enfant ne peut être appelée correctement, je pense qu'il est d'utiliser perror puis de faire cet exit(-1) non ? Et puis il faut bien mettre fin au processus enfant avec un exit.

En ce qui concerne la partie intéressante du manuel, parles-tu de : "If the value of command is NULL, system() returns nonzero if the shell is available, and zero if not. " ?

d'ailleurs j'y pense : si la commande passée au exec ne tombe par sur un exit mais juste sur le return (donc pas d'erreur), est-ce que mon processus enfant va être bien tué ?

Lern-X

Je n'ai pas compris ce que tu voulais dire. Tu parles d'un return qui serait dans le main du programme appelé via exec dans le processus fils ? Alors oui, ça aura le même effet qu'exit.

entwanne

Tout à fait c'est bien ce que je voulais dire. Mais du coup dans ce cas (donc aucune erreur que ce soit l'appel exec et l'exécution de la commande qu'on lui passe), ma fonction mySystem retournerait 0 ? Car wait(&status) écrirait 0 dans status et la macro WEXISTATUS(status) retournerait 0 ?

Quand je parle du code de retour du programme, il s'agit du status code, retournée par un programme/processus, pas par une fonction quelconque (system).

Tu omets encore une partie du man, celle qui explique justement qu'une fois le fork instancié, si le shell ne peut pas être exécuté, le processus fils doit quitter avec un code de retour 127.

Et pour ta dernière question, pas forcément 0, non. Si le return du main du processus fils retourne 1, tu obtiendras 1 comme code de retour.

man 3 exit : The exit() function causes normal process termination and the value of status & 0377 is returned to the parent (see wait(2)).

Euh … $ 0377_{8} = 255_{10}$

Donc ton nombre serra entre 0 et 255 apparemment.

+0 -0

Quand je parle du code de retour du programme, il s'agit du status code, retournée par un programme/processus, pas par une fonction quelconque (system).

Heum d'accord, dans ce cas en effet mon exit(-1), qui correspond à l'exit code (= status code ?) du processus enfant devrait être mis à EXIT_FAILURE je pense ?

Cependant, il faudrait alors que je fasse le test suivant pour que mySystem retourne -1 : "si status récupéré par wait vaut EXIT_FAILURE, alors retourner -1". Non ?

Tu omets encore une partie du man, celle qui explique justement qu'une fois le fork instancié, si le shell ne peut pas être exécuté, le processus fils doit quitter avec un code de retour 127.

Un status code/exit code ou bien un code de retour du coup ? Je pense que tu voulais parler du premier. Du coup je devrais mettre un exit(127) à la place de mon exit(-1). Mais du coup je ne mets pas EXIT_FAILURE ? :ninja:

Et pour ta dernière question, pas forcément 0, non. Si le return du main du processus fils retourne 1, tu obtiendras 1 comme code de retour.

entwanne

Oui c'est vrai. Mais du coup… le status du wait ne contient pas toujours une erreur…

Rarf j'ai l'impression que je confonds tout au secours…

@arche : ah bein là je ne comprends plus rien. Je pensais que le status code retourné à son parent par le processus fils était ce qu'il y avait dans le exit mais apparemment non ?

+0 -0

Bon, je vais essayer d'être le plus explicite possible, car ça tourne en rond.

Voici un programme d'exemple :

1
2
3
4
5
6
#include <stdlib.h>

int main(int argc, char *argv[])
{
    return atoi(argv[1]);
}

Compilé en un exécutable foo. Le return pourrait être remplacé par un exit, cela aurait le même effet.

Ce programme a comme code de retour la valeur passée en paramètre. Le code de retour de programme est équivalent à la valeur de retour du main, ou à l'argument passé à exit.

Si on teste depuis un shell :

1
2
3
4
5
6
$> ./foo 0; echo $?
0
$> ./foo 1; echo $?
1
$> ./foo -1; echo $? # Allez, pour le lol
255

Voyons maintenant la fonction system, pour laquelle j'utiliserais le code simplifié suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int system(const char *command)
{
  pid_t pid;
  int status;

  pid = fork();
  if (pid < 0)
    return -1;
  else if (!pid)
    {
      execl("/bin/sh", "sh", "-c", command, NULL);
      exit(127);
    }

  if (wait(&status) < 0)
    return -1;
  return WEXITSTATUS(status);
}

La fonction a 3 points de sortie différents :

  • Ligne 8, si le fork a échoué, -1 d'après le man (« The value returned is -1 on error (e.g., fork(2) failed) ») ;
  • Ligne 16, si la récupération du statut a échoué, -1 d'après le man ;
  • Ligne 17, on a pu lancer le fils et récupérer son statut, on retourne le code correspondant à son statut (le code de retour du programme fils, donc).

(Attention d'ailleurs pour cette dernière valeur, à bien vérifier avec un WIFEXITED que le programme s'est terminé de cette manière, et traiter les codes de signaux dans le cas contraire)

Maintenant, passons au programme fils, lignes 11 et 12, donc. La première ligne est un execl, si elle réussit, le programme indiqué est exécuté. Donc le code de retour sera celui du programme.

system("./foo 0") retournera 0, system("./foo 42") retournera 42.

La ligne 12 sera exécutée si execl échoue, donc si le shell n'a pu être lancé. Remplace "/bin/sh" ligne 11 par "toto" par exemple.

Que dit le man à ce sujet ?
« In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127). »
Donc dans le cas où le shell ne peut être instancié, system("./foo 0") retournera 127, car ce sera le code de retour du programme fils (ligne 12).

Et voilà, rien de plus. Enfin presque, il te reste à gérer le cas où command vaudrait NULL.

PS : J'ai parlé de EXIT_FAILURE avant de regarder le man et faire attention à cette histoire de 127.

Ah d'accord, tu me rassures j'avais correctement compris la plupart de ton explication.

Je vais me renseigner sur ce qu'est le "status" d'un processus, apparemment un "code" est associé à un "status" et dans notre cas, c'est le code de retour. Heum, je vais voir ça de plus près.

Je vais aussi essayer de comprendre un peu mieux la fonction wait, ainsi que la macro WIFEXITED.

Sinon après quelques recherches, j'ai trouvé ce test-là pour le cas où command vaudrait NULL :

1
if(command == NULL) { return mySystem(":"); }

C'est bien ça à ton avis ? ^^

Sinon après quelques recherches, j'ai trouvé ce test-là pour le cas où command vaudrait NULL :

1
if(command == NULL) { return mySystem(":"); }

C'est bien ça à ton avis ? ^^

Lern-X

Si c'est juste pour changer la valeur du paramètre, autant réassigner command plutôt que faire un appel récursif.

@arche : ah bein là je ne comprends plus rien. Je pensais que le status code retourné à son parent par le processus fils était ce qu'il y avait dans le exit mais apparemment non ?

Lern-X

Hum, c'est ache. M'enfin, c'est ça apparemment non. Je ne fais que lire la documentation de exit (man 3 exit dans un terminal).

+0 -0

Merci (et désolé ache) !

Du coup j'ai mis à jour l'OP avec le code-source final (partie "Edit : Topic résolu"). Finalement j'ai préféré ne pas traiter le cas command = NULL car on ne m'a pas confirmé si mon test était le bon (bien sûr si quelqu'un a le temps de m'indiquer ça je modifierais le code). Ca ne change pas grand-chose de toute façon.

Quoi qu'il en soit, un grand merci à vous tous ! :)

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte