• Aucun résultat trouvé

Exemple complet d'accès l'environnement

Dans le document Programmation système en C sous (Page 40-44)

break;

case 'f' :

if (sscanf (optarg, "%d", & fin) != 1) { fprintf (stderr, "Erreur pour fin\n");

};

break;

case 0 :

/* vitesse_lecture traitée automatiquement */

break;

case '?' :

/* On a laissé opterr à 1 */

break;

} }

fprintf (stdout, "Vitesse %d, début %d, fin %d\n", vitesse_lecture, début, fin);

return (0);

}

En voici un exemple d'exécution :

$ ./exemple_getopt_long --rapide -d 4 --fin 25 Vitesse 1, début 4, fin 25

$

Il existe également avec la GlibC une routine getopt_long_only( ) fonctionnant comme getopt_long( ), à la différence que même une option commençant par un seul tiret (-) est considérée d'abord comme une option longue puis, en cas d'échec, comme une option courte. Cela signifie que -ab sera d'abord considérée comme équivalant à --ab (donc comme une abréviation de --abort) avant d'être traitée comme la succession d'options simples «-a -b». Cet usage peut induire l'utilisateur en erreur, et cette routine me semble peu recommandable...

Sous-options

L'argument qu'on fournit à une option peut parfois nécessiter lui-même une analyse pour être séparé en sous-options. La bibliothèque C fournit dans <stdlib.h> une fonction ayant ce rôle : getsubopt( ). La déclaration n'est présente dans le fichier d'en-tête que si la constante symbolique _XOPEN_SOURCE est définie et contient la valeur 500, ou si la constante _GNU_SOURCE est définie.

L'exemple classique d'utilisation de cette fonction est l'option -o de la commande mount.

Cette option est suivie de n'importe quelle liste de sous-options séparées par des virgules, certaines pouvant prendre une valeur (par exemple -o async, noexec, bs=512).

Le prototype de getsubopt( ) est le suivant :

int getsubopt (char ** option, const char * const * tokens, char ** value);

Cette routine n'est appelée que lorsqu'on se trouve dans le case correspondant à l'option à analyser de nouveau (par -o pour mount). Il faut transmettre un pointeur en premier argument sur un pointeur contenant la sous-option. En d'autres termes, on crée un pointeur char * subopt qu'on fait pointer sur la chaîne à analyser (subopt = optarg), et on transmet & subopt à la fonction. Celle-ci avancera ce pointeur d'une sous-option à chaque appel. jusqu'à ce qu'il arrive sur le caractère nul de fin de optarg.

Le second argument est un tableau contenant des chaînes de caractères correspondant aux sous-options. Le dernier élément de ce tableau doit être un pointeur NULL.

Enfin, on transmet en dernier argument l'adresse d'un pointeur de chaîne de caractères.

Lorsque la routine rencontre une sous-option suivie d'un signe égal « = », elle renseigne ce pointeur de manière à l'amener au début de la valeur. Elle inscrit également un caractère nul pour marquer la fin de la valeur. Si aucune valeur n'est disponible, value est rempli avec NULL.

Si une sous-option est reconnue, son index dans la table tokens est renvoyé. Sinon, get subopt( ) renvoie -1. Un exemple de code permettant l'analyse d'une sous-option sera fourni dans le programme exemple_options.c décrit ci-après.

Exemple complet d'accès l'environnement

Nous allons voir un exemple de code permettant de regrouper l'ensemble des fonctionnalités d'accès à l'environnement que nous avons vues clans ce chapitre. Nous allons imaginer qu'il s'agit d'une application se connectant par exemple sur un serveur TCP/IP. comme nous aurons l'occasion d'en étudier plus loin.

Notre application doit fournir tout d'abord des valeurs par défaut pour tous les éléments paramétrables. Ces valeurs sont établies à la compilation du programme. Toutefois, on les regroupe toutes ensemble afin que l'administrateur du système puisse, s'il le désire, recompiler l'application avec de nouvelles valeurs par défaut.

Ensuite, nous essaierons d'obtenir des informations en provenance des variables d'environnement. Celles-ci peuvent être renseignées par l'administrateur système (par exemple dans /etc/profile) ou par l'utilisateur (dans ~/.profile ou dans un script shell de lancement de l'application).

Puis, nous analyserons la ligne de commande. Il est en effet important que les options fournies manuellement par l'utilisateur aient la priorité sur celles qui ont été choisies pour l'ensemble du système.

Voyons la liste des éléments dont nous allons permettre le paramétrage.

• Adresse réseau du serveur à contacter

Il s'agit ici d'une adresse IP numérique ou d'un nom d'hôte. Nous nous contenterons d'obtenir cette adresse dans une chaîne de caractères et de laisser à la suite de l'application les tâches de conversion nécessaires. Nous ne ferons aucune gestion d'erreur sur cette chaîne, nous arrangeant simplement pour qu'elle ne soit pas vide.

Par défaut, la valeur sera localhost. On pourra modifier l'adresse en utilisant la variable d'environnement OPT_ADR. Les options -a et --adresse, suivies d'une chaîne de caractères, permettront une dernière configuration.

• Port TCP à utiliser pour joindre le serveur

Le port TCP sur lequel nous désirons contacter le serveur peut être indiqué soit sous forme numérique, soit sous forme symbolique, en utilisant un nom décrit dans le fichier /etc/services. Nous considérerons donc qu'il s'agit d'une chaîne de caractères, que le reste de l'application se chargera de convertir en numéro de port effectif.

Par défaut, nous prendrons une valeur arbitraire de 4 000, mais nous pourrons modifier cette valeur en utilisant la variable d'environnement OPT_SRV , ou l'une des options -p ou - -port, suivie d'une chaîne de caractères.

• Options pour la connexion

Afin de donner un exemple d'utilisation de la fonction getsubopt( ), nous allons permettre la transmission d'une liste de sous-options séparées par des virgules. en utilisant l'option - o ou --option de la ligne de commande :

auto / nonauto il s'agit par exemple de tentative de reconnexion automatique au serveur en cas d'échec de transmission. Ce paramètre est également configurable en définissant (ou non) la variable d'environnement OPT_AUTO. Par défaut. le choix est nonauto.

delai=<duree> il s'agit du temps d'attente en secondes entre deux tentatives de reconnexion au serveur. Cette valeur vaut 4 secondes par défaut, mais peut aussi être modifiée par la variable d'environnement OPT_DELAI.

• Affichage de l'aide

Une option -h ou --help permettra d'obtenir un rappel de la syntaxe de l'application.

• Arguments autres que les options

Le programme peut être invoqué avec d'autres arguments à la suite des options, par exemple des noms de fichiers à transférer, l'identité de l'utilisateur sur la machine distante, etc. Ces arguments seront affichés par notre application à la suite des options.

Pour lire les sous-options introduites par l'option -o. une routine séparée est utilisée, principalement pour éviter des niveaux d'indentation excessifs et inesthétiques en imbriquant deux boucles while et deux switch-case.

Enfin, pour augmenter la portabilité de notre exemple. nous allons encadrer tout ce qui concerne les options longues Gnu par des directives #ifdef - #else - #endif. Ainsi, la recompilation sera possible sur pratiquement tous les systèmes Unix, à l'exception peut-être de la routine getsubopt( ). Pour compiler l'application avec les options longues, sous Linux par exemple, il suffira d'inclure une option -DOPTIONS_LONGUES sur la ligne de commande de gcc (ou dans un fichier Makefile). Sur un système où la bibliothèque C n'offre pas la routine getopt_long( ) , il suffira de ne pas définir cette constante symbolique pour permettre la compilation.

exemple_options.c #include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#ifdef OPTIONS_ LONGUES

/* (pourraient être regroupées dans un .h) */

#define ADRESSE_SERVEUR_DEFAUT "local host"

#define PORT_SERVEUR_DEFAUT "4000"

#define CONNEXION_AUTO_DEFAUT 0 #define DELAI_CONNEXION_DEFAUT 4

void sous options (char * ssopt, int * cnx_auto, int * delai);

void suite_application (char * adresse_serveur, char * port_serveur, int connexion_auto, int delai_reconnexion, int argc, char * argv []);

void affiche_aide (char * nom_programme);

int

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

/*

* Copie des chaînes d'environnement.

* Il n'est pas indispensable sous Linux d'en faire une * copie, mais c'est une bonne habitude pour assurer la * portabilité du programme.

*/

char * opt_adr = NULL;

char * opt_sry = NULL;

int opt_delai = 0;

char * retour_getenv;

/*

* Variables contenant les valeurs effectives de nos paramètres.

*/

static char * adresse_serveur = ADRESSE_SERVEUR_DEFAUT;

static char * port_serveur = PORT_SERVEUR_DEFAUT;

int connexion_auto = CONNEXION_AUTO_DEFAUT;

int delai_connexion = DELAI_CONNEXION_DEFAUT;

int option;

/*

* Lecture des variables d'environnement, on code en dur ici * le nom des variables, mais on pourrait aussi les regrouper * (par #define) en tête de fichier.

*/

retour_getenv = getenv ("OPT_ADR");

if ((retour_getenv != NULL) && (strlen (retour_getenv) != 0)) { opt_adr = (char *) malloc (strlen (retour_getenv) + 1);

if (opt_adr != NULL) {

strcpy (opt_adr, retour_getenv);

adresse_ serveur = opt_adr;

} else {

perror ("malloc");

exit (1);

}

}

retour_getenv = getenv ("OPT_SRV");

if ((retour_getenv != NULL) && (strlen (retour_getenv) != 0)) { opt_srv = (char *) malloc (strlen (retour_getenv) + 1);

retour_getenv = getenv ("OPT_AUTO");

/* Il suffit que la variable existe dans l'environnement, */

/* sa valeur ne nous importe pas.*/

if (retour_getenv != NULL) connexion_auto = 1;

retour_getenv = getenv ("OPT_DELAI");

if (retour_getenv != NULL)

option = getopt_long (argc, argv, "a:p:o:h", longopts, & index);

#else

suite_application (adresse_serveur, port_serveur, connexion_auto, delai_connexion, argc - optind, & (argv [optind]));

return (0);

} void

sous_options (char * ssopt, int * cnx_auto, int * delai) { int subopt;

break;

}

* delai = val_delai;

break;

} } }

/*

* La suite de l'application ne fait qu'afficher * les options et les arguments supplémentaires */

void

suite_application (char * adr_serveur, char * port_serveur, int cnx_auto, int delai_cnx, int argc, char * argv []) {

int i;

fprintf (stdout, "Serveur : %s - %s\n", adr_serveur, port_serveur);

fprintf (stdout, "Connexion auto : %s\n", cnx_auto ? "oui":"non");

fprintf (stdout, "Délai : %d\n", delai_cnx);

fprintf (stdout, "Arguments supplémentaires : ");

for (i = 0; i < argc; i++)

fprintf (stdout, "%s - ", argv [i]);

fprintf (stdout, "\n");

} void

affiche_aide (char * nom_prog) {

fprintf (stderr, "Syntaxe : %s [options] [fichiers...]\n", nom_prog);

fprintf (stderr, "Options :\n");

#ifdef OPTIONS_LONGUES

fprintf (stderr, " --help\n");

#endif

fprintf (stderr, " -h Cet écran d'aide \n");

#ifdef OPTIONS_LONGUES

fprintf (stderr, " --adresse <serveur> \n");

#endif

fprintf (stderr, " -a <serveur> Adresse IP du serveur \n");

#ifdef OPTIONS_LONGUES

fprintf (stderr, "--port <numero_port> \n");

#endif

fprintf (stderr, " -p <num_port> Numéro de port TCP \n");

#ifdef OPTIONS_LONGUES

fprintf (stderr, "--option [sous_options]\n");

#endif

fprintf (stderr, " -o [sous options] \n");

fprintf (stderr, " Sous-options :\n");

fprintf (stderr, " auto / nonauto Connexion automatique \n");

fprintf (stderr, " delai=<sec> Délai entre deux connexions \n");

}

Voici plusieurs exemples d'utilisation, ainsi que la ligne de commande à utiliser pour définir les constantes nécessaires lors de la compilation :

$ cc -D GNU SOURCE -DOPTIONS_LONGUES exemple_options.c -o exemple_options

$ ./exemple_options Serveur : localhost - 4000 Connexion auto : non Délai : 4

Arguments supplémentaires

$ export OPT_ADR="172.16.15.1"

$ ./exemple_options Serveur 172.16.15.1 - 4000 Connexion auto : non Délai 4

Arguments supplémentaires

$ export OPT_SRV="5000"

$ ./exemple_ options --adresse "127.0.0.1"

Serveur 127.0.0.1 - 5000 Connexion auto : non Délai : 4

Arguments supplémentaires

$ export OPT AUTO=

$ ./exemple_ options -p 6000 -odelai=5 Serveur 172.16.15.1 - 6000

Connexion auto : oui Délai 5

Arguments supplémentaires :

$ ./exemple_options -p 6000 -odelai=5,nonauto et un et deux et trois zéro Serveur : 172.16.15.1 - 6000

Connexion auto : non Délai : 5

Arguments supplémentaires : et - un - et - deux - et - trois - zéro -

$

Conclusion

Nous voici donc en possession d'un squelette complet de programme capable d'accéder à son environnement et permettant un paramétrage à plusieurs niveaux :

• à la compilation, par l'administrateur système. grâce aux valeurs par défaut

• globalement pour toutes les exécutions, par l'administrateur ou l'utilisateur. grâce aux variables d'environnement

• lors d'une exécution particulière grâce aux options en ligne de commande.

Il est important. pour une application un tant soit peu complète, de permettre ainsi à l'utilisateur et à l'administrateur système de configurer son comportement à divers niveaux.

4

Exécution des

Dans le document Programmation système en C sous (Page 40-44)