Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Petit programme en C, avez-vous des remarques etc. ?

Aucune réponse
Avatar
Francois Lafont
Bonjour à tous,

Autant le dire tout de suite, je suis vraiment totalement
inexpérimenté en langage C (pour ne pas dire plus). Ceci,
étant dit, j'ai codé un petit programme et j'aimerais bien
avoir, si c'est possible, vos suggestions et critiques. Je
mets le programme en fin de message.

C'est un programme qui se lance en ligne de commandes de cette
manière :

./mon-programme 10 localhost/cgi/machin.pl xxx yyy zzz

Le programme va alors lancer une requête http à l'adresse
http://localhost/cgi/machin.pl en utilisant la lib cURL
(ici, il s'agit d'un script Perl en localhost mais ça pourrait
être autre chose). La valeur 10 correspond à un timeout en
secondes (au delà le programme abandonne la requête), et le
reste des arguments xxx, yyy, zzz (il doit y en avoir au moins
1, mais ce n'est pas forcément 3 arguments, le nombre d'arguments
dépend de la page qui est appelée) est passé en variable POST
à la page http qui est appelée. Les variables POST seront
définies sous cette forme :

token1=xxx
token2=yyy
token3=zzz

Ensuite, les pages qui seront appelées avec ce programme
devront fournir une sortie de la forme :

- une première avec juste le nombre 0, 1, 2 ou 3;
- et des lignes suivantes avec ce qu'on veut, peu importe.

Par exemple :

2
CRITICAL blabla blabla.
Blibli blibli.

est une sortie correcte pour la page web. Ensuite, le
programme, qui aura mis cette sortie dans un buffer,
affichera uniquement les lignes autres que la ligne 1
et devra retourner comme exit code la valeur indiquée
dans la ligne 1 de la sortie de la page web. Si je
reprends l'exemple précédent, le programme devra donc
dans ce cas là afficher en sortie :

CRITICAL blabla blabla.
Blibli blibli.

tout en retournant la valeur 2 comme exit code.
Enfin, si la sortie de la page web appelée est trop
longue, alors le programme devra tronquer cette
sortie suivant une taille limite qui est indiquée
en tant que macro dans le programme.

Voilà pour ce que le programme est censé faire.

Pour l'écrire, je suis parti d'exemples que l'on
trouve ici ou là sur le web à propos de la lib cURL.
Par exemple, un point en particulier sur lequel
j'aimerais bien des avis (même si je suis vraiment
preneur de toute remarque à propos du code), c'est
l'usage du "cast". J'ai souvent entendu dire que
c'était à éviter et qu'on pouvait en général s'en
passer. Or dans le code, le cast est pas mal utilisé.
Peut-être que je m'y suis mal pris... Je n'ai pas vu
comment faire autrement par exemple au niveau d'une
fonction qui attend en argument des pointeurs de type
void*. En effet, dans le code on indique que l'on
souhaite écrire la sortie de la requête cURL dans un
buffer en indiquant le nom d'une fonction write_data
qui doit avoir cette signature :

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);

où :
- la variable buffer un pointeur vers un buffer qui
contient un bout de la sortie de la requête cURL,
- size*nmemb correspond à la taille de ce buffer,
- userp est un pointeur vers le buffer dans lequel
on souhaite écrire la sortie de cURL (c'est ce
buffer là que l'on récupère dans la fonction main).

Dans la fonction main, userp correspond à la variable
wr_buf qui est un tableau de "char" mais il doit être
casté en void* pour être passé à la fonction write_data,
sachant que, dans le corps de la fonction write_data, je
m'empresse de faire le cast « inverse » (ie je caste
userp en un pointeur de type char*). Voilà par exemple
un point sur lequel je m'interroge. Mais encore une
fois, toutes vos remarques (sur la forme comme sur le
fond) m'intéressent.

Au départ, je pensais que ce programme allait réellement
me servir mais finalement il est probable que je ne l'utilise
pas. Ceci étant, en écrivant ce programme, je me suis pris
au jeu en quelques sortes et j'ai tenté de l'achever tant
bien que mal.

Merci d'avance pour toutes vos remarques et critiques.
François Lafont

-------------------------------------------------------
/* With Debian Wheezy:
*
* sudo apt-get install libcurl4-openssl-dev
* gcc -lcurl -std=c99 -o curl-launcher.exe curl-launcher.c
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <curl/curl.h>

#define MAX_BUF 65536
#define MAX_POST_LENGTH 4096
#define OK 0
#define WARNING 1
#define CRITICAL 2
#define UNKNOWN 3

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp);
int isPositiveInteger(const char s[]);

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

if (argc < 4) {
printf("Sorry, bad syntax. You must apply at least 3 arguments.\n");
printf("%s <timeout> <url> <args>...\n", argv[0]);
return UNKNOWN;
}

if (!isPositiveInteger(argv[1])) {
printf("Sorry, bad syntax. The first argument must be a positive");
printf(" integer (it's a timeout in seconds).\n");
return UNKNOWN;
}

int timeout = atoi(argv[1]);
char *url = argv[2];

// First step, init curl.
CURL *curl;
curl = curl_easy_init();

if (!curl) {
printf("Sorry, couldn't init curl.\n");
return UNKNOWN;
}

// Construction of the post variable, a string with this form:
// token1=<urlencoded data1>&token2=<urlencoded data2>&...
char post[MAX_POST_LENGTH] = { 0 };
int token_num = 1;
char *urlencoded_str = NULL;
int i = 0;

for (i = 3; i < argc; i++) {

if (token_num > 999) {
printf
("Sorry, the limit number (999) of POST variables is exceeded.\n");
curl_easy_cleanup(curl);
return UNKNOWN;
}

//printf("C: token%d: [%s]\n", token_num, argv[i]);

urlencoded_str = curl_easy_escape(curl, argv[i], 0);

// 10 is the max length of the string "token<num>=&".
// The maximum is reached with "token999=&".
int temp_size = 10 + strlen(urlencoded_str) + 1;
char temp[temp_size];
//memset(temp, 0, temp_size*sizeof(char));
sprintf(temp, "token%d=%s&", token_num, urlencoded_str);

if (strlen(post) + strlen(temp) + 1 < MAX_POST_LENGTH) {
strcat(post, temp);
}
else {
printf("Sorry, the max POST size is exceeded.\n");
curl_easy_cleanup(curl);
return UNKNOWN;
}

curl_free(urlencoded_str);
token_num++;

}

// Remove the last character "&".
post[strlen(post) - 1] = 0;
//printf("C: POST [%s]\n", post);

char wr_buf[MAX_BUF + 1] = { 0 };

curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post);

// Tell curl that we'll receive data to the function write_data
// which will write the data in wr_buf.
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) wr_buf);

// Allow curl to perform the action.
CURLcode ret;
ret = curl_easy_perform(curl);

if (ret) {
curl_easy_cleanup(curl);
printf("Sorry, exit value of curl is %d.", ret);

switch (ret) {

case CURLE_COULDNT_RESOLVE_HOST:
printf(" Could not resolve the host address.\n");
break;

case CURLE_OPERATION_TIMEDOUT:
printf(" Operation timeout.\n");
break;

default:
printf("\n");
break;

}

return UNKNOWN;
}

curl_easy_cleanup(curl);

/*
printf("----------------------------------\n");
printf("%s", wr_buf);
printf("----------------------------------\n");
*/

int return_value;
if (!strncmp(wr_buf, "0\n", 2)) {
return_value = OK;
}
else if (!strncmp(wr_buf, "1\n", 2)) {
return_value = WARNING;
}
else if (!strncmp(wr_buf, "2\n", 2)) {
return_value = CRITICAL;
}
else if (!strncmp(wr_buf, "3\n", 2)) {
return_value = UNKNOWN;
}
else {
printf("Unexpected output of the plugin, return value not");
printf(" displayed or not in {0, 1, 2, 3}.\n");
return UNKNOWN;
}

printf("%s", wr_buf + 2);
return return_value;

}

// Write data callback function (called within the context
// of curl_easy_perform).
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp) {

// It is assumed that the type of userp is char*.
char *wr_buf = (char *) userp;

static int wr_index = 0;
int segsize = size * nmemb;

// Check to see if this data exceeds the size of our buffer. If so,
// it's possible to return O to indicate a problem to curl.
// But here, we just stop the function without error (ie, we return
// segsize) and our buffer will be troncated.
if (wr_index + segsize > MAX_BUF) {
if (MAX_BUF - wr_index > 0) {
memcpy((void *) &wr_buf[wr_index], buffer,
(size_t) (MAX_BUF - wr_index));
}
wr_index = MAX_BUF + 1; // wr_buf will be not written anymore.
return segsize;
}

// Copy the data from the curl buffer into our buffer.
memcpy((void *) &wr_buf[wr_index], buffer, (size_t) segsize);

// Update the write index.
wr_index += segsize;

// Return the number of bytes received, indicating to curl that
// all is okay.
return segsize;
}

int isPositiveInteger(const char s[]) {

if (s == NULL || *s == '\0') {
return 0;
}

int i;
for (i = 0; i < strlen(s); i++) {
// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57.
if (s[i] < 48 || s[i] > 57) {
return 0;
}
}

return 1;
}
-------------------------------------------------------

10 réponses

Avatar
Francois Lafont
Bonjour,

Le 17/07/2014 08:16, Xavier Roche a écrit :

Mais un:
post[0] = '';
suffit à priori, non ?

A partir du moment où ce tableau est une chaîne valide terminée par , toute opération de type strcat(), etc. garde cet invariant.



Ok, effectivement, tu as raison.

Même chose ici, en l'état je ne crois pas que je puisse
me passer de cette initialisation.



Il suffirait (à priori en lisant le code rapidement), dans write_data(), après le memcpy(), de coller un .



Oui, là aussi tu as raison.

A ce sujet, write_data() prend un "userp" opaque, qui permet de ne pas avoir d'horribles variables statiques. Plutôt que de passer "juste" le buffer, on peut passer une structure avec plus d'infos:

typedef struct write_data_struct {
char *buf; /* buffer */
size_t index; /* offset to data */
size_t capa; /* buffer capacity */
} write_data_struct;

Et dans le code:

...
write_data_struct wr;
wr.buf = wr_buf;
wr.index = 0;
wr.capa = MAX_BUF_IN_BYTES;

...
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &wr);

Et dans la fonction:

size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp) {
write_data_struct *const wr = (write_data_struct*) userp;
const size_t segsize = size * nmemb;

... et utiliser wr->index en place de 'wr_index', wr->capa en place de 'MAX_BUF_IN_BYTES', et wr->buf en place de ''wr_buf



Utiliser une structure semble effectivement beaucoup plus
classe. ;) J'essayerai d'écrire cela proprement... une fois
de retour chez moi. Car là il faut que je me pose sur mon
bureau avec papier + crayon etc. chose que je ne peux pas
faire actuellement.

Mais le assert n'est-il pas consommateur de ressources ?
Car je suppose qu'il sera testé à chaque appel de la
fonction, non ?



Non, cela ne coûte rien. Éventuellement on se pose la question dans une boucle interne de copie de bitmap, par exemple, mais dans une fonction qui est appelée dans le cadre de transferts réseau, un test de taille, c'est totalement transparent.



Ok je comprends. En revanche je ne sais pas si le assert
est adapté dans mon cas. En effet, si la sortie de curl
est trop grande et dépasse la taille de mon buffer, je ne
veux pas qu'il y ait d'erreur et je veux simplement que
la sortie soit tronquée.

Or, la fonction write_data doit toujours s'exécuter
sans erreur et renvoyer segsize sinon curl n'est pas
content. Du coup, dans le cas d'un dépassement du
buffer, la fonction write_data ne doit rien faire,
à part un "return segsize". Du coup, je ne crois pas
qu'un assert soit le bienvenu dans cette fonction.

Préférence personnelle également: je compile les assert() y compris en "release". Sinon ça sert à rien, un assert.

#undef assert
#define assert(exp) (void) ( (exp) || (my_abort_function(#exp, __FILE__, __LINE__), 0) )



Si je comprends bien ces 2 instructions, tu définis *ta*
propre fonction assert, c'est bien ça ?

Si je me contente d'un simple :

#include <assert.h>

ça ne suffit pas ?

[ ou, avec GCC, pour indiquer que la condition est en général vraie, mais là je chipote vraiment un peu:

#ifdef __GNUC__
#define assert(exp) (void) ( (__builtin_expect((exp), 1)) || (my_abort_function(#exp, __FILE__, __LINE__), 0) )
#endif
]



Là, j'ai pas trop compris ce chipotage.

size_t i;
size_t N = strlen(s);
for (i = 0; i < N; i++) {...

Ça irait ?



Oui, mais j'aurais écrit 'const size_t N = ...' :p

Et cela reste plus lent de toute manière: on parcours deux fois la chaîne, au lieu d'une. Et strlen() n'est fondamentalement rien d'autre que:

size_t strlen(const char *const s) {
size_t i;
for (i = 0; s[i] != '' ; i++) ;
return i;
}



Ok, je vois.
Merci encore pour toutes ces explications.

--
François Lafont
Avatar
Xavier Roche
On 07/17/2014 01:42 PM, Francois Lafont wrote:
Ok je comprends. En revanche je ne sais pas si le assert
est adapté dans mon cas.



Le assert était là pour protéger le memcpy, rien d'autre (donc il est
juste avant)

En gros le assert permet de faire crasher "proprement" un programme en
cas de problème, ce qui est considéré comme plus désirable par rapport à:
- un crash "sale" (déréférencement de pointeur NULL par exemple, qui est
difficile à tracer, ou écrasement de pile)
- une corruption silencieuse (qui pourrait se manifester longtemps après)
- un invariant qui n'est pas respecté (et qui ferait entrer le programme
dans un état bizarre)

[ Le assert n'est PAS là pour vérifier le retour d'erreur d'une fonction
par contre (enfin en règle générale - il est tolérable de assert'er un
retour non nul de malloc/realloc, selon les cas) ]

Donc ici le assert sert uniquement à vérifier que le memcpy ne pas pas
écrabouiller quelque chose qui ne t'appartient pas :)

Si je comprends bien ces 2 instructions, tu définis *ta*
propre fonction assert, c'est bien ça ?



Oui, comme ça on sait précisément ce que ça fait (NDEBUG a tendance a
être défini assez facilement sur des builds "de production", désactivant
la macro)

[ En théorie on devrait utiliser un autre nom du coup, mais je chipotte ]

Si je me contente d'un simple :
#include <assert.h>
ça ne suffit pas ?



Oui, mais faire attention à ne pas définir NDEBUG du coup (ce qui
désactiverait cette macro)
Avatar
Lucas Levrel
Le 17 juillet 2014, Francois Lafont a écrit :

[ ou, avec GCC, pour indiquer que la condition est en général vraie, mais là je chipote vraiment un peu:

#ifdef __GNUC__
#define assert(exp) (void) ( (__builtin_expect((exp), 1)) || (my_abort_function(#exp, __FILE__, __LINE__), 0) )
#endif
]



Là, j'ai pas trop compris ce chipotage.



Si j'ai bien suivi, ça dit au compilo que la branche « vrai » du test est
la plus probable, pour qu'il optimise le code produit en ce sens ; le code
de la branche « vrai » sera préférentiellement à la suite du test, le code
de la branche « faux » sera ailleurs, ce qui pénalise éventuellement la
performance s'il doit être exécuté (cache miss de la mémoire contenant le
code).

--
LL
Ἕν οἶδα ὅτι οὐδὲν οἶδα (Σωκράτης)
Avatar
Xavier Roche
On 07/17/2014 02:54 PM, Lucas Levrel wrote:
#ifdef __GNUC__
#define assert(exp) (void) ( (__builtin_expect((exp), 1)) ||
(my_abort_function(#exp, __FILE__, __LINE__), 0) )
#endif
]




Si j'ai bien suivi, ça dit au compilo que la branche « vrai » du test
est la plus probable, pour qu'il optimise le code produit en ce sens ;
le code de la branche « vrai » sera préférentiellement à la suite du
test, le code de la branche « faux » sera ailleurs, ce qui pénalise
éventuellement la performance s'il doit être exécuté (cache miss de la
mémoire contenant le code).



Oui pardon, j'avais zappé la question.

Il y a typiquement deux manière de compiler un test du type:

if (expression) {
bloc_instruction_1;
} else {
bloc_instruction_2;
}

(1) Version "équilibrée":

tester expression
si faux goto label_2
bloc_instruction_1
goto label_fin

label_2:
bloc_instruction_2

label_fin:
... suite du code

On peut éventuellement faire une version "si vrai" pour d'obscures
raisons de prédiction de branche, mais cela reviens au même: on a dans
tous les cas un "goto" (jump conditionnel)

(2) Version "déséquilibrée"

tester expression
si faux goto label_2
bloc_instruction_1

label_fin:
... suite du code

... fin du code

label_2:
bloc_instruction_2

goto label_fin

Là dans un cas on a aucun branchement, et dans l'autre on en a deux,
avec en prime un "gap" important (donc le processeur doit recharger un
autre segment de cache)

Explications très approximatives, mais c'est pour donner l'idée.
Avatar
Antoine Leca
Le 17/07/2014 01:00, Marc Espie écrivit :
In article <lq6oh3$u40$,
Olivier Miakinen <om+ wrote:
Le 16/07/2014 22:18, Benoit Izac a écrit :

// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57.
if (s[i] < 48 || s[i] > 57) {











Avec ce genre de commentaires en contexte, je ne pense pas que la
compatibilité avec les chiffres indiens ou javanais soit vraiment le
souci premier de l'OP ; mais négligeons ce détail.


Ou, un peu plus clair:
if (s[i] < '0' || s[i] > '9') {



Ou encore (plus portable ?) :


if (! isdigit(s[i])) {

Je ne pense pas que ce soit ni plus ni moins portable. C'est peut-être,
cela dit, un quart de poil de chouia de microseconde plus rapide ?



Ah si, c'est plus portable.



En l'occurrence et dans le cas _très_ particulier de isdigit(), non.
La norme C oblige les chiffres de 0 à 9 à avoir des codets consécutifs,
donc '9'-'0' vaut toujours 9 (même si '0' vaut 240 ou 0x660.) Et je ne
pense pas que quelqu'un s'est amusé à porter un compilateur C (non ANSI)
sur un encodage suffisament tordu pour que cette condition ne soit pas
respectée.
Ensuite, la norme oblige aussi à ce que isdigit() (et iswdigit()) ne
testent _que_ les chiffres '0' à '9' ; donc même s'il est plausible de
concevoir une implémentation qui testerait tous les caractères utilisés
comme chiffres décimaux (par exemple U+0660 à U+0669, U+0966 à U+096F,
etc.), ce ne serait pas conforme à la norme. Et en l'occurrence, je
pense que l'utilisation de isdigit() dans une telle implémentation
serait _moins_ portable, car normalement les programmes ne sont pas
prévus pour traiter le cas où il existe plusieurs jeux de chiffres ;
donc une telle implémentation est improbable.

Cela étant précisé, je suis d'accord sur le fond de ta remarque,
l'utilisation des fonctions isxxx() est un gage de portabilité.
Et cela incite les programmeurs à penser (comme ce devrait être acquis,
mais la pratique démontre le contraire) que le codage des caractères est
une fonction arbitraire entre signifiants et nombres, qu'il est abusif
d'utiliser l'un quand on veut en fait désigner l'autre, ou d'utiliser
des propriétés de l'un à l'autre (ici, l'opération < appliquée à des
caractères, cela n'a pas de sens en général.)


Typiquement, en EBCDIC, les chiffres sont tout en haut. Suis
pas sur que ca donne des choses tres coherentes avec des char signes
(ce qui je te l'accorde serait un peu loufoque dans ce codage).



En EBCDIC, les char sont toujours non signés et sur 8 bits, sinon cela
ne marche pas.


Note que par exemple, pour les lettres, les codes ne sont pas contigus...



Et en ISO646 aussi ! Par exemple en espagnol, le Ñ (qui est une lettre
de l'alphabet, comme chacun sait) n'est pas codé à sa place «correcte»
entre N et O... ;-)


Antoine
Avatar
Antoine Leca
Le 17/07/2014 12:05, Olivier Miakinen a écrit :
Puisqu'on fait dans l'exotique, voici un cas où comparer la
valeur avec '0' et '9' pourrait être préférable à isdigit() :
ce serait un système dans lequel isdigit() retourne vrai
lorsque la propriété Unicode 'Nombre, chiffre décimal' est
vraie (p{Nd}) :
<http://www.fileformat.info/info/unicode/category/Nd/list.htm>.



Non conforme, et de plus inutilisable dans la pratique : il y a beaucoup
trop de code C qui utilise isdigit() et implicitement suppose que la
variable examinée contient une valeur entre '0' et '0'+9

En dehors d'Unicode, il existe des codages (thaï et lao, par exemple) où
il y a deux jeux de chiffres décimaux ; mais les données informatiques
manipulées dans ces codages utilisent toujours un seul jeu de chiffres,
qui peut être l'un ou l'autre, ce qui en C implique une recompilation du
programme (en changeant la valeur de '0', un peu comme on peut parfois
changer les char de signé à non signé). Utiliser un mélange des deux
jeux n'a pas de sens, inutile donc de compliquer les implémentations.


Antoine
Avatar
Olivier Miakinen
Le 21/07/2014 12:47, Antoine Leca répondait à Marc Espie :

// ASCII value of 0 -> 48, of 1 -> 49, ..., of 9 -> 57.
if (s[i] < 48 || s[i] > 57) {











Avec ce genre de commentaires en contexte, je ne pense pas que la
compatibilité avec les chiffres indiens ou javanais soit vraiment le
souci premier de l'OP ;



... pas plus que la compatibilité avec EBCDIC.

mais négligeons ce détail.



Idem.

Ou, un peu plus clair:
if (s[i] < '0' || s[i] > '9') {



Ou encore (plus portable ?) :


if (! isdigit(s[i])) {

Je ne pense pas que ce soit ni plus ni moins portable. C'est peut-être,
cela dit, un quart de poil de chouia de microseconde plus rapide ?



Ah si, c'est plus portable.



En l'occurrence et dans le cas _très_ particulier de isdigit(), non.



Absolument.

[...]

Cela étant précisé, je suis d'accord sur le fond de ta remarque,
l'utilisation des fonctions isxxx() est un gage de portabilité.
Et cela incite les programmeurs à penser (comme ce devrait être acquis,
mais la pratique démontre le contraire) que le codage des caractères est
une fonction arbitraire entre signifiants et nombres, qu'il est abusif
d'utiliser l'un quand on veut en fait désigner l'autre, ou d'utiliser
des propriétés de l'un à l'autre (ici, l'opération < appliquée à des
caractères, cela n'a pas de sens en général.)



Je précise que, malgré mon apparente contradiction, je suis d'accord
aussi avec le fond à propos de l'utilisation des fonctions isxxx().

Je voulais juste rappeler qu'il est important, quand on programme, de
comprendre ce que font les fonctions que l'on utilise. En particulier,
depuis le temps que les ordinateurs savent gérer des jeux de caractères
autres que US-ASCII (7 bits) ou EBCDIC, il ne me semble pas sage de
supposer, sans l'avoir vérifié, que tout fonctionnera comme si les
seuls chiffres étaient '0' à '9', les seules lettres 'A' à 'Z' et 'a'
à 'z'.

[...]

Note que par exemple, pour les lettres, les codes ne sont pas contigus...



Et en ISO646 aussi ! Par exemple en espagnol, le Ñ (qui est une lettre
de l'alphabet, comme chacun sait) n'est pas codé à sa place «correcte»
entre N et O... ;-)



Oui. Et d'une langue à une autre, l'ordre alphabétique ne sera pas
forcément le même.
Avatar
Francois Lafont
Bonsoir à tous,

J'ai tenté de modifier le programme afin que l'écriture
dans le buffer passe par une structure comme me l'avait
conseillé Xavier. J'ai laissé tomber le coup des « assert »
car dans la fonction write_data, il y a déjà plein de
tests if pour éviter les écritures en dehors du buffer,
du coup ça me semblait faire totalement double emploi
(mais j'ai peut-être tort).

J'ai aussi fait en sorte que la sortie du programme
se termine pas un n si ce n'est pas déjà le cas du
buffer récupéré par curl (modification faite à la fin
du code source).

Je laisse le code ci-dessous. Si jamais vous avez des
remarques, je suis toujours preneur bien sûr. Quoi qu'il
en soit, l'exercice a été très instructif pour moi et
donc merci à tous pour toute l'aide déjà apportée dans
ce fil.

//-------------------------------------------------
/* With Debian Wheezy:
*
* sudo apt-get install libcurl4-openssl-dev
* gcc -stdÉ9 -Wextra -Wall -O2 -o sp_check sp_check.c -lcurl
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <curl/curl.h>

#define MAX_BUF_IN_BYTES 65536
#define MAX_POST_IN_BYTES 4096
#define OK 0
#define WARNING 1
#define CRITICAL 2
#define UNKNOWN 3

typedef struct Buffer Buffer;

struct Buffer
{
char wr_buf[MAX_BUF_IN_BYTES];
size_t index;
size_t capa;
};

void init_buffer ( Buffer * buffer )
{
buffer->wr_buf[0] = ''; // an empty string by default.
buffer->index = 0;
buffer->capa = MAX_BUF_IN_BYTES;
}

int is_filled ( Buffer * buffer )
{
if ( buffer->index >= buffer->capa - 1 )
{
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ''
// and can be used.
return 1;
}
else
{
return 0;
}
}

// If this function does not return segsize, it will signal an error
// condition to the library. This will cause the transfer to get aborted
// and the libcurl function used will return CURLE_WRITE_ERROR.
size_t write_data ( void *buffer, size_t size, size_t nmemb, void *userp )
{
// The size (in bytes) of the received buffer.
int segsize = size * nmemb;

// In this function, userp refers to a Buffer structure.
Buffer *userbuf = userp;

if ( userbuf->index >= userbuf->capa - 1 )
{
// userbuf is already filled.
return 0;
}

// Get truncated_segsize.
int truncated_segsize = segsize;

if ( userbuf->index + segsize >= userbuf->capa )
{
truncated_segsize = userbuf->capa - 1 - userbuf->index;
}

if ( truncated_segsize > 0 )
{
memcpy ( &( userbuf->wr_buf )[userbuf->index], buffer,
truncated_segsize );
userbuf->index += truncated_segsize;
// After each change, userbuf->wr_buf must be a valid string.
( userbuf->wr_buf )[userbuf->index] = '';
return truncated_segsize;
}
else
{
userbuf->index = userbuf->capa - 1;
return 0;
}
}

int isPositiveInteger ( const char s[] )
{
if ( s == NULL || *s == '' )
{
return 0;
}

size_t i;

for ( i = 0; s[i] != ''; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return 1;
}




int main ( int argc, char *argv[] )
{
if ( argc < 4 )
{
printf ( "Sorry, bad syntax. You must apply at least 3 arguments.n" );
printf ( "%s <timeout> <url> <args>...n", argv[0] );
return UNKNOWN;
}

if ( !isPositiveInteger ( argv[1] ) )
{
printf ( "Sorry, bad syntax. The first argument must be a positive" );
printf ( " integer (it is a timeout in seconds).n" );
return UNKNOWN;
}

const int timeout = atoi ( argv[1] );
const char *const url = argv[2];

// First step, init curl.
CURL *curl;

curl = curl_easy_init ( );

if ( !curl )
{
printf ( "Sorry, could not init curl.n" );
return UNKNOWN;
}

// Construction of the post variable, a string with this form:
// token1=<urlencoded data1>&token2=<urlencoded data2>&...
char post[MAX_POST_IN_BYTES];

post[0] = '';

int token_num = 1;
char *urlencoded_str = NULL;
int i = 0;

for ( i = 3; i < argc; i++ )
{
if ( token_num > 999 )
{
printf
( "Sorry, the limit number (999) of POST variables is exceeded.n" );
curl_easy_cleanup ( curl );
return UNKNOWN;
}

//printf("token%d: [%s]n", token_num, argv[i]);

urlencoded_str = curl_easy_escape ( curl, argv[i], 0 );

// 10 is the max length of the string "token<num>=&".
// The maximum is reached with "token999=&".
int temp_size = 10 + strlen ( urlencoded_str ) + 1;
char temp[temp_size];

sprintf ( temp, "token%d=%s&", token_num, urlencoded_str );

if ( strlen ( post ) + strlen ( temp ) + 1 < MAX_POST_IN_BYTES )
{
strcat ( post, temp );
}
else
{
printf ( "Sorry, the max POST size is exceeded.n" );
curl_easy_cleanup ( curl );
return UNKNOWN;
}

curl_free ( urlencoded_str );
token_num++;
}

// Remove the last character "&".
post[strlen ( post ) - 1] = '';

//printf("POST [%s]n", post);

curl_easy_setopt ( curl, CURLOPT_URL, url );
curl_easy_setopt ( curl, CURLOPT_TIMEOUT, timeout );
curl_easy_setopt ( curl, CURLOPT_POSTFIELDS, post );

// Tell curl that we will receive data to the function write_data
// which will write the data in "buf".
Buffer buf;

init_buffer ( &buf );
curl_easy_setopt ( curl, CURLOPT_WRITEFUNCTION, write_data );
curl_easy_setopt ( curl, CURLOPT_WRITEDATA, &buf );

// Allow curl to perform the action.
CURLcode ret;

ret = curl_easy_perform ( curl );

if ( ret )
{
if ( ret != CURLE_WRITE_ERROR || !is_filled ( &buf ) )
{
curl_easy_cleanup ( curl );
printf ( "Sorry, exit value of curl_easy_perform is %d.", ret );

switch ( ret )
{
case CURLE_COULDNT_RESOLVE_HOST:
printf ( " Could not resolve the host address.n" );
break;

case CURLE_OPERATION_TIMEDOUT:
printf ( " Operation timeout.n" );
break;

default:
printf ( "n" );
break;
}
return UNKNOWN;
}
}

curl_easy_cleanup ( curl );

/*
printf("----------------------------------n");
printf("%s", buf.wr_buf);
printf("----------------------------------n");
*/

int return_value;

if ( !strncmp ( buf.wr_buf, "0n", 2 ) )
{
return_value = OK;
}
else if ( !strncmp ( buf.wr_buf, "1n", 2 ) )
{
return_value = WARNING;
}
else if ( !strncmp ( buf.wr_buf, "2n", 2 ) )
{
return_value = CRITICAL;
}
else if ( !strncmp ( buf.wr_buf, "3n", 2 ) )
{
return_value = UNKNOWN;
}
else
{
printf ( "Unexpected output of the plugin, return value not" );
printf ( " displayed or not in {0, 1, 2, 3}.n" );
return UNKNOWN;
}

char *output = buf.wr_buf + 2;
size_t len = strlen ( output );

if ( len > 0 && output[len - 1] == 'n' )
{
printf ( "%s", output );
}
else
{
printf ( "%sn", output );
}

return return_value;
}
//-------------------------------------------------

--
François Lafont
Avatar
Xavier Roche
On 07/22/2014 02:32 AM, Francois Lafont wrote:
buffer->capa = MAX_BUF_IN_BYTES;



Juste histoire de pinailler:
buffer->capa = sizeof(buffer->wr_buf);
puisque la taille du tableau est déjà définir dans la structure.

int is_filled ( Buffer * buffer )
{
if ( buffer->index >= buffer->capa - 1 )
{
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ''
// and can be used.
return 1;
}
else
{
return 0;
}
}



Tout d'abord is_filled n'a pas besoin d'être visible par d'autres
fichiers C, donc on peut coller la fonction en "static", ce qui est plus
propre (cela ne change strictement rien sinon) car cela limite la
visiblilité

Sinon j'aurais collé quelque chose de plus compact (et finalement plus
lisible):

static int is_filled ( Buffer * buffer ) {
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ''
// and can be used.
return buffer->index >= buffer->capa - 1;
}

Voir (soyons fous):

static int is_filled ( const Buffer *const buffer ) {
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ''
// and can be used.
return buffer->index >= buffer->capa - 1;
}


size_t write_data ( void *buffer, size_t size, size_t nmemb, void *userp )



static size_t write_data ...

int segsize = size * nmemb;



const int segsize = size * nmemb;

// In this function, userp refers to a Buffer structure.
Buffer *userbuf = userp;



Buffer *const userbuf = userp;

int truncated_segsize = segsize;



const int truncated_segsize = segsize;

(oui je sais, je pinaille!)

int isPositiveInteger ( const char s[] )



static int isPositiveInteger ...

{
if ( s == NULL || *s == '' )
{
return 0;
}

size_t i;

for ( i = 0; s[i] != ''; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return 1;
}



Pinaillage: j'aurais considéré NULL comme une erreur de programmation
(l'entrée n'est pas supposée être nulle, et signale un souci de logique
de code) et supprimé le test au début du coup

static int isPositiveInteger ( const char s[] ) {
size_t i;

assert(s != NULL);
for ( i = 0; s[i] != ''; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return i != 0;
}

Bon, pinaillage pinaillage :)
Avatar
Francois Lafont
Bonjour,

Le 22/07/2014 08:08, Xavier Roche a écrit :

Juste histoire de pinailler: [...]



Pas de souci avec le pinaillage. Ton post m'a
permis au passage d'apprendre la différence entre :

const int *p = &a;
int *const p = &a;
const int *const p = &a;

Je n'avais pas fait attention jusque là (explications
trouvées assez rapidement sur le web).

buffer->capa = sizeof(buffer->wr_buf);
puisque la taille du tableau est déjà définir dans la structure.

int is_filled ( Buffer * buffer )
{
if ( buffer->index >= buffer->capa - 1 )
{
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ''
// and can be used.
return 1;
}
else
{
return 0;
}
}



Tout d'abord is_filled n'a pas besoin d'être visible par d'autres fichiers C, donc on peut coller la fonction en "static", ce qui est plus propre (cela ne change strictement rien sinon) car cela limite la visiblilité

Sinon j'aurais collé quelque chose de plus compact (et finalement plus lisible):

static int is_filled ( Buffer * buffer ) {
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ''
// and can be used.
return buffer->index >= buffer->capa - 1;
}

Voir (soyons fous):

static int is_filled ( const Buffer *const buffer ) {
// Because (buffer->wr_buf)[buffer->cap - 1] must contain ''
// and can be used.
return buffer->index >= buffer->capa - 1;
}



Ok, effectivement, quelque part c'est plus lisible.

size_t write_data ( void *buffer, size_t size, size_t nmemb, void *userp )



static size_t write_data ...

int segsize = size * nmemb;



const int segsize = size * nmemb;

// In this function, userp refers to a Buffer structure.
Buffer *userbuf = userp;



Buffer *const userbuf = userp;



Ok pour tout ça.

int truncated_segsize = segsize;



const int truncated_segsize = segsize;



Celui-là n'est pas possible car la variable est
(éventuellement) modifiée juste après. Tu étais
sans doute emporté dans ta frénésie des « const ». ;)

int isPositiveInteger ( const char s[] )



static int isPositiveInteger ...

{
if ( s == NULL || *s == '' )
{
return 0;
}

size_t i;

for ( i = 0; s[i] != ''; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return 1;
}



Pinaillage: j'aurais considéré NULL comme une erreur de programmation (l'entrée n'est pas supposée être nulle, et signale un souci de logique de code) et supprimé le test au début du coup

static int isPositiveInteger ( const char s[] ) {
size_t i;

assert(s != NULL);
for ( i = 0; s[i] != ''; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return i != 0;
}



Là aussi, je suis convaincu, du coup j'ai mis le assert.
En revanche, j'avoue que j'ai du mal avec la siouxerie
« return i != 0; ». J'ai bien compris que ça permettait
de gérer le cas où « *s == '' » et que c'est plus concis
mais je sais que dans 1 semaine j'arriverai plus à m'en
rappeler (j'ai pas trop l'habitude du côté "tout expression"
du langage C, même si ça permet des raccourcis). Du coup, je
me suis contenté de ça :

static int isPositiveInteger ( const char s[] )
{
assert ( s != NULL );

if ( *s == '' )
{
return 0;
}

size_t i;

for ( i = 0; s[i] != ''; i++ )
{
// Test if all the characters are digits.
if ( !isdigit ( s[i] ) )
{
return 0;
}
}
return 1;
}

Merci encore pour ton aide Xavier.

Le code est ici :
https://github.com/flaf/miscellaneous/blob/master/puppet/production/modules/apache_poller/files/sp_check.c
(Un lien est peut-être préférable à un copier-coller...)

--
François Lafont