4 - Les entrées-sorties

4.1 - Les différentes tables utilisées

4.1.1 - Au niveau d’un processus

Chaque processus dispose, dans son bloc de contrôle, d’une table de descripteurs qui correspondent aux fichiers qu’il utilise:

0 stdin

1 stdout

2 stderr

3 beurk

Par l’intermédiaire du descripteur, le processus accède à la table des fichiers du système.

4.1.2 - Au niveau du système

La table des fichiers du système possède autant d’entrées qu’il y a de fichiers utilisés sur la machine Unix. Chaque entrée contient notamment:

4.1.3 - Appels système et fonctions

Les primitives réalisent les opérations d’entrée-sortie de bas niveau, permettant de travailler directement en zone système, sur les caches des fichiers.

Les fonctions de la bibliothèque d’entrée-sortie, qui correspondent au fichier à inclure <stdio.h>, sont installées «au dessus» de ces appels système.

4.2 - Les opérations de base

4.2.1 - 0uverture d un fichier

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

int open(const char *chemin,int typeOpen,mode t droitsFic);

Cette primitive restitue un descripteur pour le fichier dont la référence est pointée par le paramètre chemin ; en cas de problème, elle restitue -1 et errno...

Le paramètre typeOpen définit le type d’ouverture désiré, il est écrit avec une disjonction de bits dont les mnémoniques sont définis dans le fichier <fcntl.h> :

 

Le paramètre droitsFic, qui spécifie les droits d'accès, n’est utilisé que dans le cas de la création d’un fichier ordinaire. L’inclusion du fichier <sys/stat.h> ,incluant lui-même le fichier <sys/mode.h>, autorise l’utilisation des mnémoniques de bits définis dans ce dernier fichier.

Ainsi, on peut remplacer droitsFic=0742 par :

droitsFic=S_IRWXU|S_IRGRP|S_IWOTH .

4.2.2 - Création d’un fichier régulier

Elle est réalisée soit par la primitive creat, soit par la primitive open avec le bit 0_CREAT levé.

creat("/tmp/Fic",0777); équivaut à

open("/tmp/Fic",0_WRONLY|0_CREAT|0 TRUNC,0777);

4.2.3 - Fermeture d’un fichier

#include <unistd.h>

int close(int desc);

L’appel système exit réalise la fermeture de tous les fichiers ouverts par le processus.

4.2.4 - Lecture dans un fichier

#include <sys/types.h>

#include <unistd.h>

int read(int desc,void *buffer,unsigned nb);

Attention, le paramètre buffer doit pointer sur une zone de taille suffisante pour recevoir les nb octets lus en séquence (lecture+déplacement) dans le fichier de descripteur desc ! La primitive restitue le nombre d’octets effectivement lus si tout va bien, zéro si la fin du fichier est atteinte et -1 si il y a eu un problème (avec errno...)

4.2.5 - Ecriture dans un fichier

#include <sys/types.h>

#include <unistd.h>

int write(int desc,void *buffer,unsigned nb);

4.2.6 - Déplacement de la position courante

#include <sys/types.h>

#include <unistd.h>

off_t lseek(int desc,off_t depl,int vers);

Le paramètre depl qui peut être positif, négatif ou nul précise le déplacement qui sera réalisé par rapport à une origine qui est donnée par le paramètre vers. Le fichier <unistd.h> contient trois constantes macro-définies qui sont les seules valeurs autorisées pour le paramètre vers:

La primitive renvoie la nouvelle position courante ou -1 si problème.

4.3 - Manipulation de descripteurs

4.3.1 - Duplication

#include <unistd.h>

int dup(int desc);

La primitive restitue un nouveau descripteur pour le fichier ouvert de descripteur desc. Il est garanti, et c’est primordial, que le nouveau descripteur sera le plus petit possible.

L’exemple ci-dessous montre comment on réalise une redirection de stderr sur le fichier nommé FicErr dans le catalogue courant

 

descFicErr=creat("FicErr", 0600 ) ;

close(2);

dup(descFicErr);

 

Le tableau suivant montre l'évolution de la table des descripteurs

desc

Après creat

après close

après dup

0

sdtin

sdtin

sdtin

1

stdout

stdout

stdout

2

stderr

libre

FicErr

3

FicErr

FicErr

FicErr

4

libre

libre

libre

4.3.2 - Création d’un tube local

int pipe(int desc[2])

En cas de succès, deux descripteurs sont disponibles : desc[1] (entrée du tube) et desc[0] (sortie du tube). On peut alors utiliser la primitive write pour écrire dans le tube en se servant de desc[1]. Pour placer une fin de fichier, il suffit de fermer ce descripteur. Pour lire, on utilise read sur desc[0]. Le processus créateur et sa descendance sont les seuls à pouvoir utiliser un tube local.

tube1_a

4.4 - Manipulation de i-nœuds

4.4.1 - Création d’un fichier de type tube nommé

Contrairement à un tube ordinaire, un tube nommé possède une référence et peut donc être utilisé par un processus appartenant à n’importe quel utilisateur. Néanmoins, cette technique de communication ne peut être mise en œuvre entre deux réseaux Unix.

#include<sys/types.h>

#include<sys/stat.h>

int mkfifo(const char *chemin, mode_t DroitsFic)

4.4.2 - Création d’un fichier de type catalogue

#include <sys/types.h>

#include <sys/stat.h>

int mkdir(const char *chemin,mode t droitsFic);

La primitive POSIX mkdir crée un fichier de type catalogue dont la référence est pointée par le paramètre chemin avec les droits d’accès précisés par le paramètre droitsFic (voir la primitive open).

Deux autres primitives rmdir et chdir permettent respectivement de détruire un catalogue vide et de faire d’un catalogue donné le catalogue courant.

4.5 - Consultation d'un i-nœud

4.5.1 - Introduction

Rappelons tout d’abord qu’à chaque fichier UNIX est associé un i-nombre qui indexe un i-noeud dans un système de fichiers (partition du disque dur ou disque logique).Les couples (i-nombre,référence) sont les seules choses qui soient contenues dans un fichier de type catalogue (répertoire). Le i-noeud d’un fichier contient donc tous les renseignements concernant ce fichier: propriétaire, droits d’accès...

 

L’appel système stat permet d’obtenir, dans un buffer déclaré par l’utilisateur, tous les renseignements stockés dans le i-noeud d’un fichier. En cas de problème, la valeur de retour est égale à -1 et la variable externe errno donne le numéro de l’erreur.

4.5.2 - La structure stat

Le fichier <sys/stat.h> contient la définition de la structure stat qui est utilisée pour obtenir les informations contenues dans le nœud d’un fichier.

#include <sys/types.h>

#include <sys/stat.h>

struct stat {

  dev_t st_dev, /* identification du disque logique */

  ino_t st_ino; /* i-nombre du fichier sur ce disque */

  mode_t st_mode, /* type et droits d’accès */

  nlink_t st_nlink; /* nombre de liens physiques (Hard) */

  uid_t st_uid; /* propriétaire du fichier */

  gid_t st_gid; /* groupe propriétaire du fichier */

  off_t st_size; /* tai11e du fichier */

  time_t st_atime; /* date de dernier accès */

  time_t st_mtime ; /* date de dernière modification */

  time_t st_ctime; /* date de dern. modif. du i–noeud */

}

 

Ces nombres comptent les secondes écoulées depuis le 1er Janvier 1970. La fonction ctime de la bibliothèque standard permet de les remettre sous un format habituel.

#include <time.h>

extern char  *ctime(const time_t *);

4.5.3 - La primitive stat

#include <sys/types.h>

#include <sys/stat.h>

int stat(const char *chemin, struct stat *buffer);

Le paramètre chemin pointe sur la référence du fichier à traiter. Le paramètre buffer pointe sur un objet de structure stat déjà défini.

En cas de succès, la primitive stat réalise une copie des informations concernant le fichier du i-noeud vers le buffer.

4.5.4 - Les mnémoniques

Le fichier de référence <sys/mode.h> contient un certain nombre de mnémoniques pour coder les droits d’accès, le type du fichier et le positionnement éventuel des bits spéciaux (sticky, set-uid, set-gid). Ces constantes y sont définies par des directives au précompilateur.

 

Exemples :

#define S_IRGRP 0000040

 0    0      0      0      0      4      0     octal

    0 0 0  0 0 0  0 0 0  0 0 0  1 0 0  0 0 0   binaire

                         - - -  r – –  - - -   mnémo

Droit de lecture pour les membres du groupe.

 

#define _S_IFMT 0170000

 0    1      7      0      0 0    0      0     octal

    0 0 1  1 1 1  0 0 0  0 0 0  0 0 0  0 0 0   binaire

Masque de récupération des bits concernant le type d’un fichier.

 

#define _S_IFBLK 0060000

 0    0      6      0      0      0      0     octa1

    0 0 0  1 1 0  0 0 0  0 0 0  0 0 0  0 0 0   binaire

Fichier spécial bloc.

 

Pour savoir si un fichier est de type spécial bloc:

(_S_IFMT & buffer.st_mode) == _S_IFBLK.

Pour savoir si un fichier est lisible par le groupe propriétaire:

(S_IRGRP & buffer.st_mode) == S_IRGRP

 

Le fichier <sys/mode.h> contient des fonctions macro-définies qui permettent de tester plus simplement si un fichier appartient à un type donné :

#define S_ISBLK(m) (( (m) & (_S_IFMT)) == ( _S_IFBLK))

Le programmeur remplacera donc le test donné plus haut par:

S_ISBLK(buffer.st_mode)

4.5.5 - Set-uid bit, set-gid bit, sticky bit

Le set-uid bit, lorsqu’il est positionné pour un fichier binaire exécutable, indique que le processus correspondant à son exécution possède les droits du propriétaire du fichier et non de l’utilisateur qui le lance (c’est ce mécanisme qui permet à tout utilisateur de modifier /etc/passwd, dont le super-utilisateur est propriétaire, via la commande passwd).

Le set-guid bit a le même rôle, mais relativement au groupe.

Le sticky bit assure le maintient d’un programme en zone de swap.

4.6 - Modification des caractéristiques d'un i-nœud

4.6.1 - Modification des droits d’accès

#include <sys/stat.h>

int chmod(const char *chemin,mode t droitsFic);

chmod permet de modifier les droits d’accès à condition que le propriétaire effectif du processus soit le propriétaire du fichier.

4.6.2 - Modification des propriétaires

#include <unistd.h>

int chown(const char *chemin, uid_t newPropr, gid_t newGrpPropr);

La primitive chown permet de modifier le propriétaire et le groupe propriétaire d’un fichier avec les mêmes restrictions qu’au dessus. Dans certaines versions d’Unix cette primitive est réservée à root.

4.7 -  Les fichiers de type catalogue

4.7.1 - Le fichier standard <dirent.h>

Il contient les éléments nécessaire pour la lecture du contenu des fichiers de type catalogue:

4.7.2 - Ouverture d'un fichier catalogue

#include<dirent.h>

DIR *opendir(const char *chemin);

Cette fonction réalise l'ouverture du fichier répertoire, alloue un objet de type DIR et renvoie l'adresse de cette objet. En cas d'échec, la fonction renvoie le pointeur NULL.

4.7.3 - Lecture d'une entrée

#include<dirent.h>

struct dirent *readdir(DIR *ptr);

Cette fonction lit l'entrée suivante du catalogue et renvoie l'adresse d'une structure dirent.

En fin de catalogue ou en cas d'erreur, la fonction renvoie le pointeur NULL.

4.7.4 - Fermeture d'un fichier catalogue

#include<dirent.h>

int closedir(DIR *ptr);

4.8 - La bibliothèque d'entrée-sortie standard

Cet ensemble de fonction d'E/S, très portable, constitue une couche au dessus des primitives POSIX qui ont déjà été étudiées. Il permet de diminuer le nombre d'appels système.

4.8.1 - Le fichier <stdio.h>

4.8.2 - Ouverture d'un fichier

#include<stdio.h>

FILE *fopen(const char *ref, const char *mode);

Cause l'ouverture du fichier dont la référence est pointée par ref et le renvoie d'un pointeur sur l'objet de type FILE associé à ce fichier.

En cas d'échec, la fonction renvoie NULL.

Le paramètre mode précise le mode d'ouverture dont les trois principaux sont:

4.8.3 - Fermeture d'un fichier

#include<stdio.h>

int fclose(FILE *ptr)

4.8.4 - Ouverture bibliothèque et système

Il est possible de récupérer le descripteur (niveau système) associé à un fichier ouvert au niveau de la bibliothèque

#include<stdio.h>

int fileno(FILE *ptr)

4.8.5 - Lecture dans un fichier

#include<stdio.h>

int fgetc(FILE *ptr);

char *fgets(char *buf, int taille, FILE *ptr);

int fread(void *buf, size_t taille, size_t nombre, FILE *ptr);

 

La fonction fgetc lit un caractère. Une fin de fichier ou une erreur est signalée par la valeur EOF.

La fonction fgets lit une chaîne d'au plus taille-1 caractères, un caractère ASCII nul étant ajouté en dernière position, et renvoie buf, l'adresse de cette chaîne. (NULL si échec).

La fonction fread lit nombre objets de taille caractères qu'elle écrit à partir de l'adresse buf et renvoie le nombre d'objets lus.

 

#include<stdio.h>

int fscanf(FILE *ptr, const char *format,…);

 

Cette fonction réalise une lecture formatée et renvoie soit le nombre de conversion réalisées, soit EOF en cas de fin de fichier ou d'erreur.

Il faut se rappeler qu'à partir du troisième paramètre, on passe des adresses d'objets !

La fonction scanf est un cas particulier de cette fonction avec un premier paramètre égal à stdin.

4.8.6 - Ecriture dans un fichier

#include<stdio.h>

int fputc(int c, FILE *ptr);

int fputs(char *buf, FILE *ptr);

int fwrite(void *buf, size_t taille, size_t nombre, FILE *ptr);

 

La fonction fputc écrit le caractère (unsigned char)c.

La fonction fputs écrit la chaîne pointée par buf, sans recopier le caractère ASCII nul de fin de chaîne.

La fonction fwrite écrit nombre objets de taille caractères qu'elle a trouvés à partir de l'adresse buf et renvoie le nombre d'objets écrits.

Toutes ces fonction renvoient une valeur négative en cas de problème.

 

#include<stdio.h>

int fprintf(FILE *ptr, const char *format,…);

 

Cette fonction réalise une écriture formatée et renvoie le nombre de conversion réalisées, ou une valeur négative en cas d'erreur.

Contrairement à fscanf, à partir du troisième paramètre, on passe des noms d'objets !

La fonction printf est un cas particulier de cette fonction avec un premier paramètre égal à stdout.

4.8.7 - Autre fonctions de la bibliothèque standard d'E/S

Déplacement de la position courante : fseek.

Test de la fin de fichier : feof.