_ _ _ | | | | (_) ___ _ __ | |_ _ __ ___ ___ ___ ___ ___ _ __| |_ _ ___ ___ / _ \ '_ \| __| '__/ _ \/ _ \/ __| / __|/ _ \| '__| __| |/ _ \/ __| | __/ | | | |_| | | __/ __/\__ \ \__ \ (_) | | | |_| | __/\__ \ \___|_| |_|\__|_| \___|\___||___/ |___/\___/|_| \__|_|\___||___/ -- [ Entrées/sorties standarts Principes généraux : On regroupe sous le terme d' « entrées/sorties », d une part les opérations de lecture et d'écriture, et d autre part les opérations de contrôle associées. Une opération d'écriture est un transfert de données effectué depuis une zone mémoire du programme vers une unité périphérique : terminal, unité de disque, imprimante, etc. L'opération de lecture est l'opération symétrique, c'est-à-dire un transfert de données depuis une unité de périphérique vers la mémoire. Ces opérations très bas niveaux sont réalisées par le systèmes d'exploitation lorsqu un programme exécute une requête d entrée/sortie, encore appelé entrée/sortie de « bas niveau ». Sous un système compatible POSIX, ces requêtes correspondent aux fonctions de bas niveau write et read. Les fonctions d'entrées/sorties de la bibliothèque standard, encore appelés entrées/sorties « de haut niveau » constituent dans ce cas une interface avec les entrées/sorties de bas niveau. Elles intègrent de plus, des mécanismes de formatage/dé formatage et des mécanismes d optimisation des transferts physiques. Les entrées/sorties peuvent s'effectuer sur des unités d'entrées/sorties aux comportements très différents : terminal, lecteur de disquette, lecteur de bande, fichier du système de fichier,etc. Afin d'unifier les mécanismes d'entrées/sorties de haut niveau (que nous appellerons simplement entrées/sorties dans la suite de ce document) s'effectue au moyen d un seul type d'unité logique appelée flot (de l'anglo-saxon stream). De même, toutes les unités physiques sont vues à travers une abstraction unique appelée fichier. Ce terme de fichier peut donc désigner un fichier du système de fichiers au sens strict, mais également un périphérique. Dans la suite de ce document, nous utilisons la terminologie POSIX de fichier ordinaire, répertoire et fichier spécial pour désigner respectivement un fichier de données à accès direct, un fichier référençant d'autres fichiers, et un fichier représentant une unité d'entrées/sorties. De manière générale, il existe deux types de flots : 1° Les flots texte, dont le contenu est découpé en lignes séparées par le caractère '\n'. Le contenu du flot peut subir des modifications selon les conventions de représentation de texte de l'environnement, comme par exemple la suppression des espaces précédant le caractère '\n'. 2° Les flots binaires qui permettent de transférer sans altérations le codage interne des données. Sur certains systèmes, les flots texte peuvent être restreints de sortes qu'ils peuvent seulement transmettre des caractères affichables et certains caractères de contrôles. Par contre, les flots binaires ne comportent aucune restriction. Cette distinction entre flots texte et flots binaires est imposée par la compatibilité avec des systèmes dans lesquels les applications orientées texte utilisent un format de fichier spécifique. Elle n'a pas de sens sous un système UNIX ou un système compatibilité POSIX, tous les fichiers étant gérés de façon homogène. La manière la plus élémentaire d'effectuer physiquement une entrée/sortie est de procéder caractère par caractère. Cette façon de faire peut convenir dans le cas d'entrées/sorties effectués sur le terminal. Elle s'avère par contre inefficace lors de lectures et d'écritures sur disque. Dans ce cas, il est préférable de procéder bloc par bloc. On utilise pour cela un tampon, c'est-à-dire une mémoire de travail, où sont stockés les caractères transitant par le flot. La norme ISO définit trois modes de fonctionnement possibles : 1° Non mémorisé (unbuffered) : les caractères sont transmis le plus tôt possible après la requête d'entrée/sortie ; 2° Pleinement mémorisé (fully buffered) : par défaut, les caractères sont transmis par blocs de la taille du tampon ; 3° Mémorisé par ligne (line buffered) : par défaut, les caractères sont transmis chaque fois qu'un '\n' est envoyé dans le flot ou que le tampon est plein. Les caractères peuvent également être automatiquement transmis lors des requêtes de lecture. L initialisation d'un flot est le résultat de l'ouverture d'un fichier, effectué au moyen de la fonction fopen. Un flot peut être ouvert en « lecture », en « écriture » ou en « lecture/écriture ». Cela conditionne les opérations d'entrées/sorties qu'il sera possible d'effectuer sur ce flot. Par exemple, une opération d'écriture échouera sur un flot ouvert en lecture. Le mode de fonctionnement du flot est choisi par défaut en fonction de l'unité physique correspondant au fichier. A chaque flot est associé une paire d'indicateurs composée de l'indicateur de fin de fichier et l'indicateur d'erreur, positionnées par certaines fonctions d'entrées/sorties. Diverses fonctions permettent de tester ou de réinitialiser ces indicateurs. Chaque flot possède également une position courante, généralement initialisée au début du fichier lors de l'ouverture, indiquant la position de l'octet à partir duquel sera effectuée la prochaine opération de lecture ou d'écriture. Chaque opération de lecture ou d'écriture met à jour la position courante du flot. Trois flots sont prédéfinis au démarrage d'un programme : Stdin : flot initialisé en lecture sur l'entrée standard (en général le clavier du terminal) Stdout : flot initialisé en écriture sur la sortie standard (en général l'écran du terminal ou la fenêtre du processus.) Stderr : flot initialisé en écriture immédiate sur la sortie erreur standard (en général l'écran du terminal ou de la fenêtre du processus.) Le flot stderr est toujours non mémorisé. Un flot est codé par une structure identifiée par l'identificateur de type FILE. Elle regroupe l'ensemble des informations nécessaires à la mise en œuvre d'une entrée/sortie : type du flot, fichier associé, tampon, taille du tampon, mode d'ouverture, indicateurs, position courante dans le fichier… Dans un programme, un flot est déclaré de type FILE *. Il constitue l'argument principal de toute opération d'entrée/sortie. Les flots stdin et stdout sont utilisables implicitement au moyen des fonctions scanf et printf. Diverses fonctions, comme par exemple fprintf, fscanf, fflush… permettent de lire et d'écrire sur un passé en paramètre. Les définitions nécessaires à la mise en œuvre de ces fonctions (définition du type FILE, déclaration des flots stdin, stdout et stderr…) se trouvent dans l'en-tête . Il est par conséquent nécessaire de placer la directive #include en tête de tout fichier dans lequel sont effectuées des entrées/sorties standard. Ouverture et fermeture de flot Il est possible d'initialiser un nouveau flot au moyen de la fonction fopen et de fermer un flot au moyen de la fonction fclose : #include FILE *fopen(const char *chemin, const char *mode); Int fclose(FILE *flot); Le paramètre chemin est le nom du fichier à associer au nouveau flot. Le flot peut être initialisé en mode « lecture », en mode « écriture », en mode « lecture/écriture ». Le mode « écriture » permet de créer un nouveau fichier, ou de réinitialiser un fichier existant. Le mode « lecture/écriture » permet de faire des mises à jour partielles d'un fichier. Le paramètre mode décrit le type d'accès que l'on veut effectuer : lecture et/ou écriture, et positionnement. C'est une chaîne formée de l'un des trois caractères r, w et a, suivi éventuellement du caractère +. Lors d'une ouverture en écriture, le fichier peut, soit être crée si il n'existe pas, soit encore initialisé, c'est-à-dire ramené à une taille nulle, s'il existe deja. Par défaut, chaque lecture ou écriture met à jour la position courante du flot pour la prochaine lecture ou écriture. Ce n'est pas le cas avec le mode a (« append ») qui force automatiquement le positionnement du flot a la fin du fichier lors de chaque écriture. Les modes r+, w+ et a+ correspondent à trois façons différentes d'initialiser le flot en mode lecture et écriture. Il est dans ce cas nécessaire d'effectuer une synchronisation du flot entre deux opérations de nature différentes, c'est-à-dire une lecture après une écriture ou inversement. D'autre part, le suffixe b concaténé au mode signifie « entrée/sortie en mode binaire », le mode par défaut étant le mode « texte ». Cette distinction n'a pas de sens dans une implémentation POSIX. Les différents modes d'ouverture sont résumés dans le tableau suivant : ----------------------------------------------------------------- |Mode |Accès |Positionnement |Comportement | | | | |En ecriture |Si le fichier | si le ficher | | | | |Existe |n'existe pas | ----------------------------------------------------------------- | | | | | | | R |Read | -- | -- |erreur | | | | | | | ----------------------------------------------------------------- | | | | | | |W |Write | -- |initialisation |creation | |a | |à la fin | -- |creation | | | | | | | ----------------------------------------------------------------- | | | | | | |r+ |read | -- | -- |erreur | |w+ |& | -- |initialisation |creation | |a+ |write |à la fin | -- |creation | | | | | | | ----------------------------------------------------------------- | Suffixe b : entrée - sortie binaire | ----------------------------------------------------------------- Tab. Paramétrage de l'ouverture d'un flot d'entrées/sorties. Les modes a et a+ forcent le positionnement à la du fichier lors de chaque opération d'écriture. Le mode b est sans objet sur un système compatible POSIX. Lorsque l'ouverture s'effectue sans erreur, la fonction retourne la référence à un flot. Il y a diverses situations d'erreur : chemin incorrect, fichier protégé … Dans ces cas-là, la fonction fopen retourne la valeur NULL. Par exemple, l'instruction : FILE *f_init = fopen (init_file_name, "r"); Déclare un flot de nom f_init et l'ouvre en lecture sur le fichier dont le nom est contenu dans la chaîne pointée par init_file_name. Si ce fichier n'existe pas ou n'est pas accessible alors la valeur retournée par fopen est la valeur NULL. L'opération symétrique de l'ouverture d'un flot est sa fermeture, réalisée par la fonction fclose. Cette fonction vide le tampon du flot et ferme la communication. Le descripteur de flot pourra être alloué à nouveau par la fonction fopen. Lors de la fermeture d'un tampon mémorisé, celui-ci est automatiquement vidé, il est donc naturel de fermer tous les flots ouverts avant la terminaison d'un processus. De toute manière, il faut savoir que la fermeture de flot est automatique lors d'une terminaison normale, c'est-à- dire lors du retour à la fonction main ou lors de l'appel à la fonction exit. La Macro constante FOPEN_MAX définit le nombre maximum d'ouvertures simultanées garanties par l'environnement. A savoir que sous un système compatible POSIX le nombre est au minimum 16. Il est généralement plus élevé sur un système UNIX. Sous LINUX 2.0 il est de 256. La Macro constante FILENAME_MAX définit la taille d'un vecteur pouvant contenir le plus long nom de fichier que supporte le système. Sur certains systèmes, a priori il n'y a pas de limitation de longueur pour un nom de fichier et la variable FILENAME_MAX est fixée à une valeur arbitrairement grande. Par conséquent, elle ne doit pas être utilisée pour déclarer un tampon de lecture de nom de fichier. Il est possible de redéfinir un flot déjà initialisé, en changeant le fichier qui lui est associé. Cette opération correspond à la redirection d'entrées/sorties classique sous UNIX. Pour cela, on utilise la fonction freopen : #include FILE *freopen (const char *chemin, const char *mode, FILE *flot) ; Si le descripteur de flot flot est actif, la fonction freopen commence par refermer le flot associé. Un nouveau flot est ensuite initialisé avec le même descripteur de flot, de même façon qu avec la fonction open. Si l'ouverture réussit, la référence au nouveau flot est placée dans le descripteur flot, et la fonction reopen retourne le descripteur flot. Sinon, elle retourne la valeur NULL. Il n'y a pas de notification d'erreur si la fermeture du premier flot echoue. L'exemple suivant montre une redirection du flot sortie standard dans un fichier de nom execution.log ----- reopen.c------ #include #include #define NOM_LOG "execution.log" int main(void) { freopen(NOM_LOG, "w", stdout); printf(" ) Debut d'execution de reopen )\n"); printf(" / … / \n"); printf(" ( Fin d'execution de reopen __ ( \n"); return EXIT_SUCCESS; } ----reopen.c---- La première commande exécute dans la session suivante permet de vérifier qu'aucun fichier suffixé par .log n'existe. C'est donc l'exécution de la commande reopen qui le crée. Le fichier est ensuite listé au moyen de la commande POSIX.2 cat. On peux vérifier qu'il contient toutes les lignes écrites par printf sur la sortie standard. | $ ls *.log | *.log not found | $ reopen ; ls *.log | execution.log | $ cat execution.log | ) Debut d'execution de reopen ) | / … / | ( Fin d'execution de reopen __ ( | $ | Un programme peut délibérément ignorer les redirections d'entrées/sorties standard effectuées depuis l'interprète de commandes, en effectuant une réouverture du terminal auquel il est attaché. Par exemple sous UNIX, le terminal attaché étant représenté par le fichier spécial /dev/tty l'exécution de : freopen("/dev/tty", "w" , stdout) rend un programme insensible aux redirections de sa sortie standard. La commande noredir suivante recopie une chaîne reçue en argument (ou son entrée standard dans le cas où il n y a pas d'argument) sur sa sortie standard. Auparavant, elle effectue une réouverture de cette sortie standard sur le terminal attaché au processus. -----noredir.c----- #include #include #define TTY "/dev/tty" int main(int argc, char *argv[]) { freopen(TTY, "w", stdout); if (argc >1) { int i; for (i = 1; i < argc; i++) printf("%s", argv[i]); printf("\n"); } else { for(;;) { int c = getchar(); if ( c == EOF) break; putchar(c); } } return EXIT_SUCCESS; } -----noredir.c----- L'exemple d'exécution suivant illustre l'impossibilité de détourner les sorties du programme noredir, en effectuant une redirection sur le fichier spécial /dev/null (Sous UNIX, le fichier spécial /dev/null est associé à une unité d'entrée/sortie qui fait disparaître toutes les données qu'il reçoit). Les données lues par le programme sont typographiés en italiques soulignées. | $ Les écrits s'envolent parfois … | Les écrits s'envolent parfois… | $ echo Les écrits s'envolent parfois … > /dev/null | $ | $ noredir > /dev/null | Des mots | Des mots | que l'on ne peut détourner . . . | que l'on ne peut détourner … | $ noredir Des mots que l'on ne peut détourner … > /dev/null | Des mots que l'on ne peut détourner… | $ Voila pour une petite présentation sur les entrées/sorties standard avec quelques subtilités peu connues. J'espere avoir été assez clair dans ma présentation et que vous en tirerez quelque chose. Pour toutes critiques, avis, conseils mais aussi félicitations :p je suis ouvert a tout:p Mais a vous de me trouver .. La petite partie obligatoire et qui me tiens a cœur les GreetZ :D Special GreetZ to n0name : bleyme, Night_Fall, ScanX, Tolwin, Icing & P41f0X (SecurityHack) , Drim (RedKod) -- [ Yp3rite