Salut à tous,
j'ai acheté le livre de Christophe Blaess ( en personne ) sur la programmation système.
Je suis tombé sur un truc intéressant page 695-696-697 du bouquin dans le chapitre 23 intitulé "Communication classique entre processus" concernant une "possibilité rarement proposée par les shell". Selon moi, à première vue, il s'agissait seulement de ce à quoi pourrait faire face un simple fichier ".sh". Mais en me penchant un peu sur la question, je me suis rendu compte des facultés d'un tel algorithme. Pipé des commandes pré-enregistrées dans une bdd au format ".json", créées à partir d'autres programmes, afin de les faire communiquer etc…
Tu rends compte? Faire fonctionner la modularité du "c", en "c" même, ce sur un système basé sur ce procédé, j'ai nommé LINUX…
Je vous présente, ci-après, comment je procède. En attendant voici le code disposé dans trois fichier:
-
fichier "process.h":
#ifndef PROCESS_H #define PROCESS_H #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/stat.h> #include<string.h> #include<errno.h> #include<sys/wait.h> #include<sys/types.h> #include"file.h" typedef struct { char ***commands; int **args; char **result; int len; int size; int execcount; } process_datas; int process_pipe(char** commands, int fd[2], pid_t *son); int process_popen(char ** result, char** commands, int args, int *size); int process_finished(pid_t pid); int process_datas_internal(process_datas *p, char **result, int *size, int i); int process_append_cmd(char * param, process_datas * datas, int new_command); int process_exec(process_datas *p); int process_datas_destroy(process_datas *p); int process_datas_init(process_datas *p); #endif
-
fichier "process.c":
#define _GNU_SOURCE #include"process.h" #include"file.h" extern char **environ; int process_pipe(char** commands, int fd[2], pid_t *son) { int tube_1[2]; int tube_2[2]; if ((pipe(tube_1) != 0) || (pipe(tube_2) != 0)) return EXIT_FAILURE; switch(*son = fork()) { case -1: close(tube_1[0]); close(tube_1[1]); close(tube_2[0]); close(tube_2[1]); fprintf(stderr, "fail to fork in process_pipe\n"); return errno; break; case 0: ; setpgid(0, 0); close(tube_1[1]); close(tube_2[0]); dup2(tube_1[0], STDIN_FILENO); dup2(tube_2[1], STDOUT_FILENO); execve(commands[0], commands, environ); perror("execve in process_pipe"); return errno; break; default: ; setpgid(*son, *son); close(tube_1[0]); close(tube_2[1]); fd[0] = tube_2[0]; fd[1] = tube_1[1]; } return EXIT_SUCCESS; } int process_popen(char ** result, char** commands, int args, int *size) { FILE* fd; int len = 0; for(int i = 0; i < args - 1; i++) { len += strlen(commands[i]) + 2; } char *command = malloc(len); char *c = command; for(int i = 0; i < args - 1; i++) { sprintf(command, "%s ", commands[i]); command += strlen(commands[i]) + 1; } if((fd = popen(c, "r")) != NULL) { if(file_fgetsn(result, fd, size)) { free(command - strlen(c)); pclose(fd); perror("fgetsn in process_popen"); return EXIT_FAILURE; } }else{ free(command - strlen(c)); perror("popen in process_popen"); return EXIT_FAILURE; } free(command - strlen(c)); pclose(fd); return EXIT_SUCCESS; } int process_append_cmd(char *param, process_datas *datas, int new_command) { if(!new_command) { if(datas->size) { int args = datas->args[datas->size - 1][1]++; if(args > datas->args[datas->size - 1][0]) { datas->args[datas->size - 1][0] += 1; datas->commands[datas->size - 1] = (char**)realloc(datas->commands[datas->size - 1], datas->args[datas->size - 1][0] * sizeof(char*)); } datas->commands[datas->size - 1][datas->args[datas->size - 1][1] - 1] = (char*) NULL; } datas->size++; if(!datas->len) { datas->len = datas->size; datas->args = (int **) calloc(datas->len, sizeof(int*)); datas->args[0] = (int*) calloc(2, sizeof(int)); datas->args[0][0] = 7; datas->args[0][1] = 1; datas->commands = (char***) calloc(datas->len, sizeof(char**)); datas->commands[0] = (char**) calloc(7, sizeof(char*)); }else if(datas->size > datas->len) { datas->len = datas->size; datas->args = (int **)realloc(datas->args, datas->len * sizeof(int*)); datas->args[datas->size - 1] = (int *)calloc(2, sizeof(int)); datas->args[datas->size - 1][0] = 7; datas->args[datas->size - 1][1] = 1; datas->commands = (char***) realloc(datas->commands, datas->len * sizeof(char**)); datas->commands[datas->size - 1] = (char **) calloc(7, sizeof(char*)); } }else{ int args = datas->args[datas->size - 1][1]++; if(!(args - datas->args[datas->size - 1][0])) { datas->args[datas->size - 1][0] += 7; datas->commands[datas->size - 1] = (char**) realloc(datas->commands[datas->size - 1], datas->args[datas->size - 1][0] * sizeof(char*)); } } datas->commands[datas->size - 1][datas->args[datas->size - 1][1] - 1] = strdup((char *)param); return EXIT_SUCCESS; } int process_finished(pid_t pid) { errno = 0; int status; while(1) { usleep(2000); pid_t check; if((check = waitpid(pid, &status, WNOHANG|WUNTRACED)) > 0) { if(WIFEXITED(status)) { printf("%ld terminated successfully : %s\n", (long) pid, "signal OK !"); return EXIT_SUCCESS; }else if(WIFSIGNALED(status)) { printf("%ld terminated by signal : %s\n", (long) pid, strsignal(WTERMSIG(status))); }else if(WIFSTOPPED(status)) { printf("%ld stopped by signal : %s\n", (long) pid, strsignal(WSTOPSIG(status))); }else if(WIFCONTINUED(status)) { printf("%ld continued\n", (long) pid); } }else if(check == -1){ return errno; } } return EXIT_FAILURE; } int process_exec(process_datas *p) { int ret = 0; char * result = NULL; int size= 0; int args = p->args[p->size - 1][1]++; if(args > p->args[p->size - 1][0]) { p->args[p->size - 1][0] += 1; p->commands[p->size - 1] = realloc(p->commands[p->size - 1], p->args[p->size - 1][0] * sizeof(char*)); } p->commands[p->size - 1][p->args[p->size - 1][1] - 1] = (char*) NULL; p->execcount++; p->result = realloc(p->result, p->execcount * sizeof(char*)); if((ret = process_datas_internal(p, &result, &size, 0)) == EXIT_FAILURE) { free(result); fprintf(stderr, "Error in first command in process_exec: not / or bad internal process"); return EXIT_FAILURE; }else if(ret == 254 || ret == 255 || ret == EXIT_SUCCESS){ if((ret = process_popen(&result, p->commands[0], p->args[0][1], &size)) == EXIT_FAILURE) { if(result != NULL) free(result); fprintf(stderr, "Error in first command in process_exec from process_popen\n"); return EXIT_FAILURE; } } if(p->size == 1) { p->result[p->execcount - 1] = strdup(result); free(result); process_datas_init(p); return EXIT_SUCCESS; } int tube[2]; pid_t son; int error; for(int i = 1; i < p->size; i++) { if((ret = process_datas_internal(p, &result, &size, i)) == EXIT_FAILURE) { free(result); fprintf(stderr, "Bad internal process"); return EXIT_FAILURE; }else if(ret != EXIT_SUCCESS && ( i + 1 ) < p->size) ++i; if((ret = process_pipe(p->commands[i], tube, &son))) { free(result); fprintf(stderr, "fail to process_pipe in process_exec"); return errno; } if(fd_writen(tube[1], result, strlen(result), 1)) { free(result); fprintf(stderr, "fail to write in fd in process_exec"); return EXIT_FAILURE; } close(tube[1]); if((error = process_finished(son)) != EXIT_SUCCESS) { free(result); return error; } if(fd_readn(&result, tube[0], &size)) { free(result); fprintf(stderr, "fail to read from fd in process_exec"); return EXIT_FAILURE; } close(tube[0]); } p->result[p->execcount - 1] = strdup(result); free(result); process_datas_init(p); return EXIT_SUCCESS; } int process_datas_internal(process_datas *p, char **result, int *size, int i) { if(!strcmp(p->commands[i][0], "file")) { FILE *f = fopen(p->commands[i][1], "w+"); if (f == NULL) { free(*result); fprintf(stderr, "Error opening file in process_exec\n"); return EXIT_FAILURE; } fprintf(f, *result); fclose(f); return 254; }else if(!strcmp(p->commands[i][0], "cd")){ chdir(p->commands[i][1]); return 255; }else if(!strcmp(p->commands[i][0], "concat")){ int len = 0; for(int l = 1; l < p->args[i][1] - 1; l++) len += strlen(p->commands[i][l]) + 2; if(*size < len) *result = calloc(len, sizeof(char)); for(int l = 1; l < p->args[i][1] - 1; l++) { if(l == 1) strcpy(*result, p->commands[i][l]); else strcat(*result, p->commands[i][l]); } return 256; } return EXIT_SUCCESS; } int process_datas_init(process_datas *p) { for(int i = 0; i < p->size; i++) { for(int j = 0; j < p->args[i][1]; j++) { free(p->commands[i][j]); p->commands[i][j] = NULL; } p->args[i][1] = 1; } p->size = 0; return EXIT_SUCCESS; } int process_datas_destroy(process_datas *p) { for(int i = 0; i < p->len; i++) { for(int j = 0; j < p->args[i][1]; j++) { if(p->commands[i][j] != NULL) { free(p->commands[i][j]); p->commands[i][j] = NULL; } } free(p->commands[i]); p->commands[i] = NULL; } free(p->commands); p->commands = NULL; for(int i = 0; i < p->len; i++) { free(p->args[i]); p->args[i] = NULL; } free(p->args); p->args = NULL; p->len = 0; p->size = 0; for(int i = 0; i < p->execcount; i++) { free(p->result[i]); p->result[i] = NULL; } free(p->result); p->result = NULL; p->execcount = 0; return EXIT_SUCCESS; }
-
fichier "file.c"
#include"file.h" int file_read(char **result, int *size) { FILE * file = (void*) NULL; struct stat status; char * content = (void*) NULL; if (stat(*result, & status) != 0) { perror("stat"); return EXIT_FAILURE; } if ((content = malloc(status.st_size)) == NULL) { perror("malloc"); return EXIT_FAILURE; } if ((file = fopen(*result, "r")) == NULL) { free(content); perror("fopen"); return EXIT_FAILURE; } if ((signed int)fread(content, 1, status.st_size, file) != status.st_size) { free(content); perror("fread"); return EXIT_FAILURE; } *size = status.st_size; *result = content; fclose(file); return EXIT_SUCCESS; } int file_fgetsn(char **result, FILE* fd, int *size) { int i = 0, j = 0; char buffer[64] = {'\0'}; if(!(*size >= 128)) { j = 128; *result = calloc(j, sizeof(char)); }else{ j = *size; } if(*result) { while(fgets(buffer, 64, fd) != NULL) { if(!i) strcpy(*result, buffer); else strcat(*result, buffer); i += strlen(buffer); if((j - i) < 64) { j = i + 128; *result = realloc(*result, j); if(*result == NULL) { perror("realloc in file_fgetsn"); return EXIT_FAILURE; } } } }else{ perror("malloc in file_fgetsn"); return EXIT_FAILURE; } *size = j; return EXIT_SUCCESS; } int fd_writen(const int sd, const char * b, const size_t s, const int retry_on_interrupt) { size_t n = s; while (0 < n) { ssize_t result = write(sd, b, n); if (-1 == result) { if ((retry_on_interrupt && (errno == EINTR)) || (errno == EWOULDBLOCK) || (errno == EAGAIN)) { continue; } else { break; } } n -= result; b += result; } if(0 < n){ perror("write in fd_writen"); return EXIT_FAILURE; }else{ return EXIT_SUCCESS; } } int fd_readn(char** result, int fd, int *size) { int m = 0; char buffer[2] = {'\0'}; while(read(fd, buffer, sizeof(char))) { if(!m) strcpy(*result, buffer); else strcat(*result, buffer); m += strlen(buffer); if((*size - m) < 64) { *size = m + 128; *result = realloc(*result, *size); } if(*result == NULL) { perror("realloc in file_read"); return EXIT_FAILURE; } } return EXIT_SUCCESS; }
exemple "test.c":
#define _DEFAULT_SOURCE
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include"process.h"
int main(int argc, char **argv)
{
int ret;
process_datas p = {NULL, NULL, NULL, 0, 0, 0};
char *cwd = calloc(1,256);
getcwd(cwd, 256);
process_append_cmd("concat", &p, 0);
process_append_cmd("j'adore ", &p, 1);
process_append_cmd("windows", &p, 1);
process_append_cmd("/bin/sed", &p, 0);
process_append_cmd("-es;windows;linux;g", &p, 1);
if(!(ret = process_exec(&p)))
{
printf("%s\n", p.result[0]);
process_append_cmd("concat", &p, 0);
process_append_cmd(p.result[0], &p, 1);
process_append_cmd(" avec", &p, 1);
process_append_cmd(" enthousiasme !!", &p, 1);
if(!(ret = process_exec(&p)))
{
printf("%s\n", p.result[1]);
}else{
free(cwd);
process_datas_destroy(&p);
fprintf(stderr, "failed: %s \n", strerror(ret));
exit(EXIT_FAILURE);
}
}else{
free(cwd);
process_datas_destroy(&p);
fprintf(stderr, "failed: %s \n", strerror(ret));
exit(EXIT_FAILURE);
}
process_datas_destroy(&p);
free(cwd);
return EXIT_SUCCESS;
}
Alors je sais que c'est un peu imbuvable question longueur, mais imaginez le potentiel en récursif…
Alors mon problème, qui ne représente rien face à la tâche à accomplir, est que je n'arrive pas à libérer les variables concaténées ( d'où l'utilisation qui fonctionne dans le programme "test.c" ci-dessus ) lorsqu'il s'agit de simples caractères au niveau de l'appel à "process_datas_init" dans "process_exec".
Si quelqu'un pouvait m'aider cela me permettrait de gagner du temps sur mon projet qui est largement entamé ( sous d'autres perspectives ) et en même temps de rendre hommage à un édifice du monde linuxien.
Merci d'avance.
PS: L'interface finale est destinée à être programmée en ncurses
# Isoler le code problématique
Posté par j_m . Évalué à 9.
Est-ce que tu pourrais isoler et reproduire ton problème dans un nombre minimal de ligne?
C'est assez courrant de faire ça avant de demander de l'aide sur un forum. Ça aiderait les gens à comprendre ton problème.
[^] # Re: Isoler le code problématique
Posté par Chris K. . Évalué à 1. Dernière modification le 29 août 2019 à 20:00.
Quand tu galères sur les strings et tu te fais tailler un short ^^
Oui moi aussi quand j'ai vu la longueur du code j'ai décidé de pas lire. Et de laisser un commentaire à la con !
# Reallocation sans free ?
Posté par David Marec . Évalué à 7. Dernière modification le 28 août 2019 à 23:00.
Et, de quoi s'agit-il ?
Oui. Essayez de garder le même style. Ici, la position des
{
, par exemple, est aléatoire.utilisez des
goto
pour la gestion d'erreur avec libération de ressources, ce sera plus lisible et vous risquerez moins d'en oublier une.Préférez
strncat
etstrncpy
qui vous éviterons des ennuis, d'autant que vous recalculez la longueur juste avant.Je sens un problème dans
process_datas_internal
.Je suppose que
result
a été alloué ailleurs et passé en paramètre. Dans ce cas, à quoi rime lecalloc
? Il provoque une nouvelle allocation deresult
sans que l'ancien ne soit libéré et n'a pas de sens avec une taille de 1 (sizeof(char)
) . Ce n'est pas plutôt un realloc que vous vouliez faire ?Au passage, plutôt que de faire des
if
dans lesfor
sur la variable de loop. Traitez les séparément:Pourquoi passer
size
par référence alors que vous ne l'utilisez que par variable ?[^] # Re: Reallocation sans free ?
Posté par gunsailor . Évalué à 2.
Merci David pour toutes ces suggestions.
```
Il s'agit tout simplement de l'emploi du pipe, ce afin de rediriger l'entrée ET la sortie ( c'est là la nouveauté ) d'une sous routine de mon programme père. Ce système permet l'exécution de sous-routines à la chaîne, et donc de créer un programme composé uniquement de sous-routines - que j'appellerais modules du programme. En concaténant plusieurs modules, on peut arriver à créer un nouveau programme basé sur ces mêmes modules.
Mon programme n'est qu'un utilisateur de sous programmes/routines/modules.
Il est destiné à l'édition ( via un utilitaire d'édition en ncurses ) de nouveaux programmes dont les paramètres ( aide, modules au format json avec messages d'erreur pour chacun d'entre eux etc... ) seront classés au sein d'une base de donnée.
Chaque sous routine devra dès lors être imbriquée dans une liste doublement chaînée afin de faciliter la correction des erreurs, l'éditeur permettant à tout instant de lancer un programme partiel ( ou pas ) afin d'en vérifier les résultats intermédiaires.
J'ai même envie de donner à mon module le nom "concat", la fonctionnalité qui m'a amené à rédiger ce topic.
```J'essaierai de faire mieux la prochaine fois, en me rappelant du conseil donné plus haut par j_m et en gardant un formatage décent.
J'ai fait comme vous me l'avez préconisé.
Vous aviez raison pour le calloc… En effet, result peut avoir été alloué au préalable.
voici le code spécifique à concat dans internal:
En ce qui concerne size, j'avais oublié de lui affecter len. size est un marqueur qui me permet de savoir si result mérite d'être ré-alloué dans fd_readn. Dans ce cas, il est incrémenté en conséquence dans cette dernière fonction avant la prochaine itération de la boucle de process_exec.
Merci de votre attention.
Le problème n'est toujours pas résolu.
[^] # Re: Reallocation sans free ?
Posté par David Marec . Évalué à 3.
Je vois toujours un
calloc
qui ferait office demalloc
dans ce code, tout en négligeant de libérer le pointeur précédent au préalable.calloc
avec une taille d’élément à1
, soit la taille d'unchar
, c'est unmalloc
.Et il existait (existe?) d'autres
calloc
douteux que celui de cette fonction.Dans cette branche,
len
vaut0
quoi qu'il arrive non ?Donc, c'est équivalent à
malloc(1)
?[^] # Re: Reallocation sans free ?
Posté par gunsailor . Évalué à 1.
Bonjour Mr Marec,
je vais tenter d'expliquer cette partie du code:
1. result: résultat en chaîne de caractères des opérations précédentes de l'exécution de la chaîne de modules courante - je nommerai ces dernières "listing". Il est censé être ré-allouer dès que nécessaire et libéré après conservation dans le tableau process_datas->result de ma structure process_datas, ce à la fin de l'exécution de chaque listing.
2. size: taille du string result nécessaire à la fonction fd_readn pour le calcul d'un espace supplémentaire éventuel - le but étant de limiter les libérations.
1. len: somme des longueurs des strings à concaténer ( ne peut pas être égal à 0 ):
- code de la fonction interne concat revisité:
remarquez l'incrémentation du i de la boucle de process_exec et dont je vérifie la validité dans cette même dernière fonction ( améliorée ), afin d'éviter de passer par le pipe si jamais le listing courant se terminait par une commande internal:
Mon programme se comporte mieux ( il fonctionne ). Par contre, je vais devoir m'arracher les cheveux pour trouver le pointeur non libéré que m'indique valgrind:
merci encore une fois de votre attention. Si vous souhaitez collaborer à la conception de ce module destiné à la création de nouveaux modules… n'hésitez pas à m'en faire part.
[^] # Re: Reallocation sans free ?
Posté par David Marec . Évalué à 2.
Clairement: tout ce que je vous indique est valide, quoi que vous fassiez en amont.
Ré-allouer, c'est appeler
realloc
, pascalloc
. Je le répète: vos appels àcalloc
n'ont pas de sens:1
(sizeof (char)
): utilisezmalloc
.result
n'est pasnull
donc queresult
se doit d'être libéré avant d'appelercalloc
.realloc
fonctionne comme un malloc si le pointeur n'avait pas été alloué en premier lieu. Vous pouvez l'utiliser partout. (mais ça donnera l'impression que vous ne maîtrisez pas vous allocations.)Et cela se retrouve dans plusieurs parties de votre code.
C'est vous qui le dite, pas votre code:
Si en fait, vous vouliez allouer
result
parce qu'il ne l'était pas ou plus, vérifiez le directement:Enfin:
En fait, memcheck.
Pour avoir plus de précision sur l'origine de la fuite, utilisez
--leak-check=full
et consorts.[^] # Re: Reallocation sans free ?
Posté par David Marec . Évalué à 3.
Faites mieux: vérifiez vous même, à l'aide d' assertion.
Par exemple, avant d'allouer:
ou avant d'affirmer:
Dans un premier temps, mettez vos sources à disposition en téléchargement, de préférence avec un système de gestion de version au dessus.
[^] # Re: Reallocation sans free ?
Posté par gunsailor . Évalué à 1.
Merci pour votre patience.
Le code rectifié est le suivant:
avec size affecté en dernier.
J'espère que c'est plus clair.
Le programme est maintenant fonctionnel et sans leak.
Je vais poster, dès la création de la page, le lien github de ce petit bout de code à destination de ceux qui haïssent le bash.
[^] # Re: Reallocation sans free ?
Posté par David Marec . Évalué à 2.
qui doit être équivalent à ces seules lignes:
Ce qui change tout. Ceci dit, je reste persuadé qu'il vaut mieux encadrer
result
plutôt quesize
pour déterminer qui est déjà alloué et qui ne l'est pas encore.moins il a de branches, mieux se porte le code.
[^] # Re: Reallocation sans free ?
Posté par gunsailor . Évalué à 1.
Bon alors là j'ai dû mal me faire comprendre. size est la taille de result. Si size vaut 0 alors result est égal à null. Mais je veut bien concevoir votre aspect de la réalité:
et le calloc reste essentiel !
[^] # Re: Reallocation sans free ?
Posté par David Marec . Évalué à 3.
Peut-être, mais c'est la valeur de
result
qui est importante, c'est lui la clef - et le résulat- des allocations. Dîtes vous que c'est (ou ça devrait être ) l'inverse:result
estNULL
alorssize
vaut0
.Et non !
si result est NULL:
Et, je le répète:
sizeof(char)
vaut1
, quoiqu'il arrive. C'est le même la seule taille de type qui soit garantie.calloc
maismalloc
.ptr=realloc(NULL,size)
est équivalent àptr=malloc(size)
Man realloc:
[^] # Re: Reallocation sans free ?
Posté par gunsailor . Évalué à 1.
Merci, je ne connaissais pas cette fonctionnalité de realloc.
Aussitôt dit aussitôt fait.
Je suis en train d'éditer un programme exemple assez conséquent histoire de montrer les possibilité du module.
Je reviens pour poster le lien github.
[^] # Re: Reallocation sans free ?
Posté par fearan . Évalué à 2.
Meuh non
est ton ami :) pour plus d'options : valgind --help
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Reallocation sans free ?
Posté par gunsailor . Évalué à 1.
merci pour ces précisions.
Un peu de lecture ne me fera pas de mal.
# Voici ce à quoi m'aura servi ce topic
Posté par gunsailor . Évalué à 2.
Je vous livre ici le code de mon application.
Faites en bon usage.
Merci encore
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.