Syt`emes 1 — Fichiers

Transcription

Syt`emes 1 — Fichiers
Sytèmes 1 — Fichiers
20 décembre 2013
Table des matières
1 Gestion d’erreurs sous UNIX
2
2 La structure de système de fichiers sous Linux.
2.1 Les droits sur les fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Descripteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3 Ouverture et fermeture d’un fichier régulier ou d’une tube nommée (open/close) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3.1 Droits d’accès . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4 Fermeture de fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.5 umask - masque de création de fichier . . . . . . . . . . . . . . . . . . . . .
2.6 Lecture de fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.7 Écriture dans un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.8 Copier un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.9 access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.10 La position courante dans le fichier (offset) et les déplacements de la position
courante à l’aide de lseek . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.11 Quelques remarques sur les types . . . . . . . . . . . . . . . . . . . . . . . .
3
5
6
13
14
3 Les répertoires
3.1 Suppression, création et le parcours d’un répertoire . . . . . . . . . . . . . .
3.2 Répertoire courant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
15
17
4 La structure de système de fichier
4.1 inodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 struct stat et les fonctions stat fstat lstat – consultation d’informations d’inode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3 Création/suppression/changement de nom de lien dur . . . . . . . . . . . .
4.3.1 Création/renommage de liens durs – link, rename . . . . . . . . .
18
18
1
6
7
8
8
10
11
11
13
19
22
22
5 Les descripteurs, les fichiers ouverts, les inodes – vue générale
5.1 Dernières remarques sur les liens durs . . . . . . . . . . . . . . . . . . . . .
23
25
6 Les
6.1
6.2
6.3
26
26
27
bits : set-uid, set-gid — le propriétaire réel et le propriétaire effectif
Les bits setuid et setgid . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Sticky bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Changement de caractéristiques de fichier : droits d’accès, propriétaire, dates
d’accès . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7 Nom d’utilisateur, le répertoire initial, le shell
8 Liens symboliques
8.1 Création du lien symbolique . . . . . . . . . . .
8.2 Consultation des attributs d’un lien symbolique
8.3 La lecture du lien symbolique . . . . . . . . . .
8.4 Modifications des propriétés de lien symbolique
9 Lecture/écriture non bloquantes
1
28
29
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
29
29
31
33
34
34
Gestion d’erreurs sous UNIX
Si une erreur survient pendant l’exécution d’une fonction dans la plupart de cas la
fonction retourne une valeur négative ou NULL. La variable et errno déclarée dans le fichier
en-tête errno.h contient le code d’erreur. Les codes d’erreur et errno sont définis dans le
fichier <errno.h>. La définition historique de errno est
extern int errno;
mais avec les threads chaque thread peut avoir ses propre erreur donc errno n’est peut pas
être une variable (sinon errno serait partagé entre les threads).
Deux règles d’utilisation de errno.
(1) La valeur d’errno n’est jamais mis automatiquement à 0. Après un appel système qui
termine avec erreur errno contient le code d’erreur jusqu’à ce que un nouvel appel
système qui termine avec erreur ne mette pas un nouveau code d’erreur. Pour cette
raison on examine errno uniquement si la valeur de retour de la fonction signale une
erreur.
(2) Il n’y a pas de code d’erreur 0. Donc nous pouvons toujours mettre 0 dans errno sans
que cela soit confondu avec un code d’erreur.
2
#include <string.h>
char *strerror(int errnum)
transforme le numéro d’erreur en message.
#include <stdio.h>
void perror(const char *msg)
affiche msg concaténé au message d’erreur fourni par le système.
Dans mes programme j’utilise souvent la macro-fonction suivante pour afficher le message d’erreur. Elle affiche non seulement le message d’erreur comme le fait perror mais
aussi le fichier et la ligne dans le code (les macro-constantes __FILE__ et __LINE__) :
/∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗∗∗ ∗∗∗ ∗ p a n i c . h ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗∗∗ ∗∗ ∗∗∗∗/
#i f n d e f PANIC H
#define PANIC H
#include <s t d i o . h>
#include < s t d l i b . h>
#include <s t r i n g . h>
#include <e r r n o . h>
#define PANIC( f i n ) do{
\
f pr in t f ( stderr ,\
”\n e r r o r i n f i l e %s i n l i n e %d : %s \n” , \
F I L E , LINE , s t r e r r o r ( e r r n o ) ) ; \
i f ( f i n > 0) e x i t ( f i n ) ;
\
} while ( 0 )
#endif
2
La structure de système de fichiers sous Linux.
Le système de fichiers UNIX est hiérarchisé et composé de fichiers et de répertoires. La
hiérarchie commence avec le répertoire racine (root) dont le nom est / . Deux noms sont
créés dans chaque répertoire à sa création : . (dot) et .. (dot-dot), le premier fait référence
au répertoire courant le deuxième au répertoire parent. (Dans le répertoire racine et ..
(dot-dot) et . (dot) pointent les deux vers le répertoire courant.)
Les chemins absolus ce sont les chemin qui commencent par / comme /usr/bin/ash.
Le chemin relatif ce sont les chemins dont la référence ne commence pas par /. Chaque
processus possède le répertoire courant current working directory. Le chemin relatif est
3
résolu par rapport au répertoit courant du processus 1 .
Par exemple si le processus utilise le chemin relatif ../toto/bin/cfd.c et le répertoire
courant est /home/kowalski alors le chemin absolu correspondant est
/home/kowalski/../toto/bin/cfd.c.
Différents types de fichiers que nous pouvons rencontrer : fichiers réguliers, répertoires,
liens symboliques (soft links), fichiers spéciaux caractère et fichiers spéciaux bloc comme
les fichiers dans le répertoire /dev, tubes nommés, les sockets etc.
Dans ce cours nous nous intéressons de fichiers réguliers, répertoires, liens symboliques
et tubes nommés et anonymes.
Les sockets sont étudiés dans le cours programmation réseau.
Le type de fichier est affiché si on liste un répertoire avec la commande ls -l. Le type
est indiqué par la lettre qui précède les droits d’accès :
code
d
s
p
c
b
l
type de fichier
fichier régulier
répertoire
socket
tube nommée (named pipe)
fichier spécial type caractère
fichier spécial type block
lien symbolique
Linux peut gérer plusieurs systèmes de fichiers en même temps : Ext2, Ext3, NTSF, etc.
Chaque système de fichier réside sur un disque logique (disque physique peut être divisé en
plusieurs disques logiques). Un de ce systèmes est le système principale monté à la racine
/. Mais nous pouvons monter d’autres systèmes de fichiers avec la commande mount. Dans
mon Linux (Ubuntu) d’autres systèmes de fichiers sont montés dans le répertoire /media
et constituent les sous-arbres de l’arbre principal de fichier. Mais avec la commande mount
il est possible de monter les systèmes de fichiers dans n’importe quel répertoire.
Sur mon portable le système NTFS où réside MSWindows est monté sur /media/OS,
c’est-à-dire /media/OS devient la racine de ce système de fichiers.
Chaque système de fichier possède un format interne différent pour les répertoires et
fichiers. POSIX donne une interface qui permet d’accéder aux fichiers de différents types
de façon uniforme indépendamment de systèmes de fichiers dans lequel les fichiers résident.
1. La commande bash pwd parmet d’afficher ce répertoire et cd permet de changer le répertoire courant
depuis le terminal. La Section 3.2 décrit les fonctions getcwd et chdir qui permettent de trouver et changer
le répertoire courant d’un processus.
4
2.1
Les droits sur les fichiers
Propriétaire et groupe propriétaire de fichier.
priétaire et un groupe propriétaire.
Chaque fichier possède un pro-
Les droits de la lecture, écriture et exécution. ls -l permet d’afficher les droits
d’accès à un fichier sous forme rwxrwxrwx donnant les droits respectivement pour le propriétaire (user), le groupe propriétaire (group) et les autres (other) (dans cet ordre, de
gauche à droite).
Exemple. Supposons que les droits sur un fichier toto 2 sont --x-w-r--. Alors le propriétaire possède le droit x, tous les membres du groupe propriétaire à l’exclusion du propriétaire lui-même possèdent le droit w et finalement tous les autres c’est-à-dire tous excepté
le propriétaire et les membres de groupe propriétaire possèdent le droit r.
rwx pour un fichier régulier. La signification de rwx pour un fichier régulier est
évidente : droit de lecture de fichier, d’écriture et d’exécution.
– Par exemple pour ouvrir le fichier avec les flags 0_RDONLY ou O_RDWR il faut avoir le
droit de lecture.
– Pour ouvrir le fichier avec les flags O_TRUNC ou O_RDWR ou O_WRONLY il faut avoir le
droit d’écriture.
rwx pour un répertoire. La signification de rwx pour un répertoire est moins évidente.
Le répertoire peut être vu comme une table composée de couples
(nom_de_fichier, pointeur)
où pointeur pointe vers un fichier 3 .
Dans le cas d’un répertoire le droit de lecture r c’est le droit de lire la liste des entrées
de ce répertoire. Par exemple il suffit d’avoir le droit de lecture sur le répetoire pour faire ls
simple (sans options) sur ce répertoire (ls lit les entrées de répertoire donc pour exécuter
ls sans erreur il faut que celui qui lance ls possède le droit de lecture sur le répertoire).
Pour le répertoire le droit écrire w c’est le droit de modifier le tableau qui implémente
le répertoire, où modifier signifie ajouter/supprimer/modifier les entrées dans ce tableau.
En particulier il faut avoir le droit d’écriture w sur le répertoire pour ajouter ou supprimer une entrée dans un répertoire (i.e. créer ou supprimer un fichier dans un répertoire).
2. quand je parle de fichiers alors cela signifie tout type de fichiers, y inclue les répertoires
3. Cela ne veut pas dire que le répertoire est vraiment implémenté comme une table, pour la raison
d’efficacité l’implémentation peut être différente. Mais pour nous il est commode de voir le répertoire
comme un tableau.
5
Pareil si on veut changer le nom de fichier dans le répertoire il fait avoir le droit w su le
répertoire.
Le droit x pour un répertoire signifie d’avoir le droit de passage par le répertoire. Donc,
par exemple, pour pouvoir lire un fichier 4 qui se trouve dans un répertoire il faut avoir le
droit x sur ce répertoire (et sur tous les répertoires qui mènent vers ce fichier).
Par exemple pour ouvrir le fichier /home/dupont/sources/myprog.c il faut avoir les
droits de passage x sur les trois répertoires /, /home et /home/dupont/sources.
En conclusion :
– nous pouvons créer un nouveau fichier dans un répertoitre si nous avons les droits wx
sur ce répertoire, w parce que cette opération modifie la liste des entrées du répertoire
et x pour pouvoir passer dans le répertoire.
– pour supprimer le fichier d’un répertoire il faut aussi les droits wx sur le répertoire,
w parce que l’opération supprime une entrée du répertoire et x pour pouvoir passer
dans le répertoire. Par contre nous n’avons besoin ni droit de lecture ni d’écriture sur
le fichier lui-même pour le supprimer.
2.2
Descripteurs
Un descripteur est un entier non négatif que le système associe avec un fichier ouvert.
Trois descripteurs sont définis par des constantes symboliques dans unistd.h :
STDIN_FILENO STDOUT_FILENO STDERR_FILENO
pour l’entrée standard, sortie standard et sortie d’erreurs standard. Dans tous les systèmes
UNIX les valeurs de ces trois constantes sont respectivement 0, 1, 2 mais on préférera utiliser
les constantes symboliques.
2.3
Ouverture et fermeture d’un fichier régulier ou d’une tube nommée
(open/close)
Un processus doit ouvrir un fichier pour pouvoir lire ou écrire :
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const *char chemin, int cmd, ... /* mode_t droits */)
4. et plus en général pour ouvrir le fichier
6
La fonction open alloue et retourne le descripteur de fichier. Le descripteur de fichier
est un petit entier non-négatifs qui sert de référence vers le fichier ouvert. En cas d’échec
open retourne −1.
Le deuxième paramètre cmd de open permet de spécifier de différents flags (drapeaux)
d’ouverture. Exactement un de flag doit être un des flags qui désignent le type d’ouverture :
– O_RDONLY – le fichier ouvert en lecture uniquement,
– O_WRONLY – le fichier ouvert en écriture seule,
– O_RDWR – le fichier ouvert en lecture et écriture.
L’information sur le mode d’ouverture ne pourra pas être modifiée après l’ouverture.
D’autres flags d’ouverture sont spécifiés en utilisant OR bit à bit | avec le mode d’ouverture :
O_TRUNC
si le fichier est ouvert en mode O_RDWR ou O_WRONLY alors à l’ouverture
le fichier sera tronqué (la taille de fichier sera ramenée à 0)
O_CREAT
si le fichier n’existe pas alors le fichier sera crée. Dans ce cas il faut
donner le troisième paramètre de open qui spécifie les droits de fichier
créé. Le masque de umask est appliqué, voir section 2.5. Le fichier créé a
comme propriétaire et groupe de propriétaire le propriétaire et groupe
de propriétaire effectifs du processus qui crée le fichier, voir section 6.
O_EXCL
si les flags O_CREAT et O_EXCL sont spécifiés et le fichier existe déjà alors
open échoue et retourne −1
Les flags ci-dessus ne sont pas modifiables après l’ouverture de fichier.
Les flags suivants sont mémorisées et peuvent être modifiés par un appel à la fonction
fcntl :
O_APPEND
toute opération d’écriture se fera à la fin de fichier,
O_NONBLOCK le fichier sera ouverture en mode non bloquant.
Exemple. int d = open("toto", O_RDWR|O_TRUNC|O_APPEND);
Le fichier toto sera ouvert en lecture et écriture, à l’ouverture la taille de fichier devient
0 (l’ancien contenu du fichier est effacé), toutes les écritures dans le fichier seront effectuées
à la fin de fichier.
2.3.1
Droits d’accès
Si le fichier est ouvert avec le flag O_CREAT il faut spécifier les droits sur le fichier créé
en utilisant le troisième paramètre de open. Les droits sont spécifiés à l’aide de macroconstantes suivantes :
– S_IRUSR S_IWUSR S_IXUSR – respectivement read, write, exec pour le propriétaire,
la macro-constante S_IRWXU est équivalente à S_IRUSR|S_IWUSR|S_IXUSR,
– S_IRGRP S_IWGRP S_IXGRP – read, write, exec pour le groupe propriétaire, S_IRWXG
est équivalent à S_IRGRP|S_IWGRP|S_IXGRP
7
– S_IROTH S_IWOTH S_IXOTH – read, wrire, exec pour les autres, S_IRWXO est équivalent
à S_IROTH|S_IWOTH|S_IXOTH
Exemple. int d=open(‘‘toto’’,O_CREAT|O_WRONLY, S_IWUSR|S_IRUSR) demande l’ouverture de fichier toto en écriture. Si le fichier n’existe pas alors le fichier sera créé avec les
droits lecture/écriture pour le propriétaire.
La fonction creat
int creat(const *char chemin, mode_t droits)
L’appel int d=creat(chemin, droits) est équivalent à
open(chemin, O_WRONLY | O_CREAT | O_TRUNC, droits)
2.4
Fermeture de fichier
Si le descripteur n’est plus utilisé il faut le fermer :
#include <unistd.h>
int close(int descriptor)
La fonction close retourne 0 en cas de succès et −1 en cas d’erreur.
2.5
umask - masque de création de fichier
Chaque processus possède un masque qui est appliqué au moment de la création de
processus crée un fichier ou un répertoire.
Les droits d’accès accordés à la création sont obtenus en appliquant le masque sur les
droits demandés.
Pour connaı̂tre la valeur de la masque associé à votre terminal on utilise la commande
UNIX umask. Sur mon portable j’obtiens :
umask
0022
Donc le masque est 0022 en octal, ce qui donne 000010010 en binaire. En comparant
ceci avec les droits d’accès rwxrwxrwx nous pouvons vois que les bits 1 correspondent aux
droits w pour le groupe propriétaire et pour les autres. Cela signifie que à la création de
fichiers/répertoire les droits w ne seront pas accordés ni pour le groupe propriétaire ni pour
les autres mêmes si on le demande.
Depuis le programme C on gère le masque à l’aide de la fonction :
8
#include <unistd.h>
mode_t umask(mode_t mask)
L’argument de la fonction c’est le nouveau masque et la fonction retourne l’ancien masque.
Les droits réellement appliqués pendant la création de fichier sont obtenus en évaluant
l’expression
demande & ~umask
où demande les droits demandés (pour un rappel : & c’est l’opération ET bit à bit et ~ c’est
la négation de bit à bit).
Exemple. Soit
umask
0022
et mon programme C crée un fichier toto :
int desc = open("toto", O_WRONLY|O_CREAT,
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
en demandant les droits rw-rw-rw- (lecture et écriture pour user,group,other), en binaire
110110110. Mais après la création le fichier toto aura les droits rw-r--r-- puisque
110110110 & ~000010010=110110110 & 111101101=110100100
Le masque 0022 empêche d’accorder les droits w pour le groupe et les autres.
Pour que les droits d’accès soient positionnés selon notre demande il faut temporairement modifier le masque en le mettant à 0 :
mode_t m;
int desc;
/* changer le masque et mémoriser l’ancien masque*/
m=umask(0000);
desc = open("toto", O_WRONLY|O_CREAT,
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
/*revenir à l’ancien masque*/
umask(m);
Maintenant le nouveau fichier toto aura les droits rw-rw-rw-
9
2.6
Lecture de fichiers
#include <unistd.h>
ssize_t read(int descriptor, void *tampon, size_t nombre)
Le primitif read envoie
– le nombre de caractères lus si la lecture réussit,
– 0 à la fin de fichier,
– −1 en cas d’erreur.
Les types size_t et ssize_t sont définis dans <sys/types.h>, size_t c’est un type
entier non signé, ssize_t est un type entier signé.
Les paramètre de read :
1. le descripteur de fichier ouvert en lecture,
2. tampon - l’adresse de mémoire où read place les données lues, cette adresse doit être
valide (read ne fait pas d’allocation de la mémoire),
3. nombre la taille de tampon en octets.
POSIX.1.2001 ajoute d’autres fonctions de lecture.
#include <sys/uio.h>
ssize_t readv(int descriptor, const struct iovec *vecteur, int iovcnt)
#include <sys/uio.h>
struct iovec {
char *iov_base;
/*addresse en memoire */
size_t iov_len;
/*nombre de caractères à lire */
}
La fonction readv lit dans les tampons spécifiés dans vecteur.
vecteur[i].iov_base donne l’adresse de ième tampon, vecteur[i].iov_len donne sa
longueur.
#include <unistd.h>
ssize_t pread(int descripteur, void *tampon, size_t nombre, off_t pos)
10
La fonction pread fait la même chose que read mais la lecture se fait à partir de la position
pos. De plus pread ne change pas la position courante (offset) de fichier qui reste la même
qu’avant l’appel à pread.
Le type off_t est un type signé entier utlisé surtout pour désigner la taille d’un fichier
ou la position courante dans le fichier.
2.7
Écriture dans un fichier
#include <unistd.h>
ssize_t write(int descripteur, void *tampon, size_t nombre)
write retourne le nombre d’octets écrits dans le fichier, −1 en cas d’erreur.
S’il n’y a pas de verrou (exclusif, partagé) alors : l’écriture soit à la position courante
soit à la fin si le flag O_APPEND a été spécifié à l’ouverture de fichier.
S’il y a un verrou est écriture est bloquante alors write bloque en attendant la libération
de verrou. Si verrou et non bloquant alors write retourne −1 et errno==EAGAIN
#include <sys/uio.h>
ssize_t writev(int descripteur, const struct iovec *vecteur, int n)
vecteur c’est un vecteur de structures iovec, chaque structure décrit un tampon, int n
donne le nombre d’éléments dans le vecteur.
#include <unistd.h>
ssize_t pwrite(int descripteur, void *tampon, size_t n, off_t position)
pwrite fait la même chose que write mais l’écriture se fait à la position donnée par le
dernier argument. de plus pwrite ne change pas la position courante dans le fichier.
2.8
Copier un fichier
Le programme suivant copie un fichier en utilisant les descripteurs de fichiers. La taille
de tampon est passée comme le paramètre de main ou, à défaut, elle est égale à 1024.
Si le tampon est de taille d’un octet alors sur mon portable le temps d’exécution affiché
avec time est
real 0m6.388s
user 0m0.768s
sys 0m5.592s
11
pour un fichier de taille 2594272 octets. Avec le tampon de 1024 octets le temps est
real 0m0.026s
user 0m0.000s
sys 0m0.024s
pour le même fichier.
#define
#include
#include
#include
#include
#include
#include
#include
POSIX C SOURCE 200112L
<s y s / t y p e s . h>
<s y s / s t a t . h>
< f c n t l . h>
<u n i s t d . h>
< s t d l i b . h>
<s t d i o . h>
” p a n i c . h”
#define TAILLE 1024
i nt main ( i nt a r g c , char ∗ a r g v [ ] ) {
i nt fd1 , fd2 , r c , wc ;
char ∗tamp ;
i nt t ;
i f ( a r g c == 4 ) {
t=a t o i ( a r g v [ 3 ] ) ;
}
e l s e i f ( a r g c == 3 )
t = TAILLE ;
else{
f p r i n t f ( s t d e r r , ” usa g e : \ n %s f i c h i e r i n f i c h i e r o u t [ t a i l l e d e t a m p o n ] \ n” ,
argv [ 0 ] ) ;
exit (1);
}
i f ( ( tamp = m a l l o c ( t ) ) == NULL)
PANIC ( 1 ) ;
i f ( ( f d 1 = open ( a r g v [ 1 ] ,O RDONLY) ) < 0 )
PANIC ( 2 ) ;
i f ( ( f d 2 = open ( a r g v [ 2 ] ,O WRONLY | O CREAT | O TRUNC,
S IRWXU | S IRWXG | S IRWXO ) ) < 0 )
PANIC ( 3 ) ;
for ( ; ; ) {
12
r c = r ea d ( fd1 , tamp , t ) ;
i f ( rc < 0 )
PANIC ( 4 ) ;
i f ( r c == 0 )
break ;
wc=w r i t e ( fd2 , tamp , r c ) ;
i f ( wc < 0 )
PANIC ( 5 ) ;
}
c l o s e ( fd1 ) ;
c l o s e ( fd2 ) ;
f r e e ( tamp ) ;
return 0 ;
}
2.9
access
Pour déterminer si un processus possède un accès à un fichier on peut utiliser la fonction
#include <unistd.h>
int access(const char *chemin, int mode)
Le paramètre mode est un OU bit à bit I de macro-constantes suivantes :
– F_OK pour tester l’existence,
– R_OK, W_OK, X_OK pour tester le droits de lecture, écriture, exécution.
La fonction retourne 0 si le test d’accès est positif et −1 sinon.
2.10
La position courante dans le fichier (offset) et les déplacements de
la position courante à l’aide de lseek
Chaque fichier ouvert possède la position courante (offset). Juste après l’ouverture la
position courante est au début de fichier (offset est 0).
Chaque read s’effectue à partir de la position courante et il change la position courante,
le nouveau offset est juste après le dernier octet lu. En particulier si la position courante
est à la fin de fichier (juste après le dernier caractère du fichier) alors read retournera 0.
Si le fichier est ouvert sans flag O_APPEND alors l’écriture dans le fichier s’effectue à
partir de la position courante et le nouveau offset sera juste après le dernier caractère écrit.
Si le fichier est ouvert avec le flag O_APPEND alors write déplace la position courante
à la fin de fichier, ensuite write écrit et le nouveau offset est après le dernier octet écrit,
donc à la fin de fichier.
On peut modifier la position courante sans lire ou écrire à l’aide de :
13
#include <unistd.h>
off_t lseek(int descripteur, off_t position, int origine)
Le paramètre origine est une des trois constantes :
SEEK_SET
SEEK_CUR
SEEK_END
par rapport au début de fichier
par rapport à la position courante
par rapport à la fin de fichier
En cas d’erreur la valeur (off_t) -1 est envoyée, sinon la fonction envoie la position
courante après le déplacement.
Exemple. off_t pos=lseek(desc, (off_t) -20, SEEK_CUR);
déplace la position courante de 20 octet vers le début de fichier.
Par contre
off_t pos=lseek(desc, (off_t) 20, SEEK_SET);
place la position courante 20 octets après le début de fichier.
Il est impossible de se placer à une position < 0, par contre il est bien possible de passer
à une position au-delà de la taille de fichier.
Si on écrit dans le fichier est la position courante est supérieures à la taille de fichier
alors le “trou” sera rempli par les caractères nul ’\0’.
Exemple. char *s=’’abcdef’’;
lseek(d,SEEK_END,20);
write(d,s,strlen(d));
Si d est un descripteur ouvert en écriture (et sans flag O_APPEND) alors ce fragment
de programme ajoute les caractères abcdef à la fin de fichier. Mais en plus comme les
caractères sont ajoutés 20 octets après l’ancien fin de fichier, les caractères abcdef seront
précédés par 20 caractères \O qui remplissent le trou. Bien sûr après write l’offset est à la
fin de fichier, juste après le dernier caractère écrit f.
2.11
Quelques remarques sur les types
D’après Single UNIX Specification :
– size_t – utilisé pour la taille d’objet, type entier non signé.
– ssize_t — utilisé pour compter les octets ou pour indiquer erreur, type signé entier.
C’est difficile de voir comment size_t et d’autres de ces types sont définis en regardant
les fichiers en-tête, trop de conditions à suivre. Une solution c’est voir comment les macros
se développent :
14
cpp -dD -std=c99 copy.c > toto.txt
et dans toto.txt j’ai trouvé les lignes
typedef unsigned int size_t;
typedef int __ssize_t;
typedef __ssize_t ssize_t;
typedef long int __off_t;
typedef __off_t off_t;
3
Les répertoires
3.1
Suppression, création et le parcours d’un répertoire
La création :
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
mkdir retourne 0 si OK et −1 eb cas d’échec. Le deuxième paramètre mode_t sert à spécifier
les droits d’accès au répetoire, on utilise les mêmes constantes que pour les fichiers, voir
section 2.3.1.
La suppression :
#include <unistd.h>
int rmdir(const char *pathname);
Retourne 0 si OK et −1 sinon. Le répertoire doit être vide.
La lecture d’un répertoire :
#include <dirent.h>
DIR *opendir(const char *pathname);
struct dirent *readdir(DIR *dp);
void rewinddir(DIR *dp);
int closedir(DIR *dp);
15
opendir retourne un pointer si OK, NULL si error.
readdir retourne un pointer si OK, NULL si la fin du répertoire ou une erreur.
La structure struct dirent retournée par readdir est définie comme :
struc dirent {
ino_t d_ino;
char d_name[NAME_MAX + 1];
}
/* i-node number */
/* null-terminated filename */
dépend de l’implementation. d_name existe toujours mais pas d_ino.
La valeur de NAME_MAX n’est pas importante mais elle peut-être trouvée avec fpathconf.
La lecture de répertoire s’effectue en trois étapes :
– ouverture avec opendir,
– la lecture en boucle avec readdir, chaque appel à readdir retourne une nouvelle
entrée dans le répertoire, le champ d_name de struct dirent donne le nom de
l’entrée. A la fin de parcours readdir retournera NULL.
– fermeture avec closedir.
A noter que les deux entrées . (dot) et .. (dot dot) sont aussi récupérées pendant le
parcours de répertoire.
#define
#include
#include
#include
#include
#include
#include
#include
#include
#include
POSIX C SOURCE 1
<e r r n o . h>
<s y s / t y p e s . h>
<s y s / s t a t . h>
<d i r e n t . h>
<u n i s t d . h>
<s t d i o . h>
< s t d l i b . h>
<s t r i n g . h>
” p a n i c . h”
/∗ a f f i c h e r l e con t en u d ’ un r e p e r t o i r e donne comme paramet re de main
∗ ls
∗/
s t a t i c i nt p a r c o u r s ( const char ∗ nom rep ) ;
i nt main ( i nt a r g c , char ∗ a r g v [ ] ) {
i nt i ;
for ( i =1; i < a r g c ; i ++){
parcours ( argv [ i ] ) ;
}
16
return EXIT SUCCESS ;
}
s t a t i c i nt p a r c o u r s ( const char ∗ nom rep ) {
DIR ∗ f l o t ;
struct d i r e n t ∗ e n t r e e ;
/∗ o u v r i r r e p e r t o i r e ∗/
i f ( ( f l o t = o p e n d i r ( nom rep))==NULL)
PANIC ( 1 ) ;
errno = 0;
for ( ; ; ) {
e n t r e e = r e a d d i r ( f l o t ) ; /∗ l i r e une e n t r e e de r e p e r t o i r e ∗/
i f ( e n t r e e == NULL) {
i f ( e r r n o ) { /∗ e r r e u r de p a r c o u r s ∗/
PANIC ( 2 ) ;
} e l s e { /∗ f i n de p a r c o u r s ∗/
closedir ( flot );
return 0 ;
}
}
i f ( strcmp ( e n t r e e −>d name , ” . ” ) == 0
| | strcmp ( e n t r e e −>d name , ” . . ” ) == 0 )
continue ;
p r i n t f ( ”%s/%s \n” , nom rep , e n t r e e −>d name ) ;
}
e x i t (EXIT SUCCESS ) ;
}
3.2
Répertoire courant
Chaque processus possède le répertoire courant. Ce répertoire est utilisé pour évaluer
les chemins relatifs comme par exemple ../../toto. Le processus hérite son répertoire
courant de son parent.
#include <unistd.h>
char *getcwd(char *tampon, size_t taille)
int chdir(const char *chamin)
La fonction getcwd met dans le tampon le chemin absolu vers le répertoire courant. Le
deuxième paramètre indique la taille du tampon. getcwd retourne NULL et errno==ERANGE
si taille du tampon n’est pas suffisante pour stocker le chemin. Dans ce cas il convient
d’augmenter le tampon.
17
La fonction chdir permet de changer le répertoire courant.
Exemple. L’exécution de
chdir(getenv(‘‘HOME’’))
place le processus dans le répertoire d’accueil de l’utilisateur.
4
4.1
La structure de système de fichier
inodes
Avec chaque fichier ouvert le système associe une structure appelé inode (ou vnode).
Dans inode le système stocke plusieurs informations concernant le fichier :
– st_nlink – le nombre de liens liens durs qui pointent sur le fichier,
– st_size – la taille en octets (si cela a un sens pour le type donné de fichiers),
– st_dev – device ID, identifie le volume (le disque logique) où réside le fichier,
– st_ino – le numéro de inode, c’est un numéro que le système attribue au fichier. Pour
le même disque logique les numéros inodes sont tous différents, il n’y a jamais deux
fichiers avec le même numéro de inode pour le même disque logique. Le numéro de
inode peut être vu comme l’identifiant de fichier sur un disque logique donné. Ceci
implique que chaque fichier est identifié par le couple (device ID, le numéro de inode).
– st_uid, st_gid – ID de propriétaire et ID du groupe propriétaire de fichier,
– trois dates :
– st_atime – le date du dernier accès au fichier,
– st_mtime – la date de la dernière modification de données,
– st_ctime la date de la dernière modification d’attributs, c’est-à-dire la dernière
modification du noeud lui-même (création/suppression d’un lien, changement de
droits etc.)
– le type de fichier (fichier regulier, répertoire, lien symbolique, tube, fichier spécial,
etc.),
– les drapeaux de droits lecture/écriture/exécution pour le propriétaire, groupe propriétaire et les autres,
– les flags setuid, setgid et sticky bit.
Toutes les dates depuis EPOCH (le 1 janvier 1970).
La structure réelle d’informations qui sont stockées pour chaque fichier dépend de
système de fichiers, inode est une abstraction qui donne les informations qui sont indépendantes
de système de fichier.
Notez que le nom de fichier n’est pas stocké dans inode, en effet la seule information
permettant d’identifier le fichier c’est son numéro de inode et l’identifiant de volume.
18
Factures
Autres
Enfants
Loisirs
Maison
portable
loyer
gaz
cine
Figure 1 – Un sous-arbre de l’arbre de fichier. Les onglets marquent les répertoires.
Exemple. Supposons que l’arborescence de fichiers contient un répertoire Factures. La
figure 1 montre le sous-arbre de l’arbre de fichiers à partir du répertoire Factures.
La figure 2 page 36 montre le même arbre plus en détail, avec le contenu de chaque
répertoire et une partie de inode de chaque fichier.
Les liens qui partent d’un répertoire vers un fichier ce sont les liens durs (hard link).
Notez que le nombre de liens durs vers un répertoire vide est 2, un lien depuis son père
et un autre lien depuis le répertoire lui même (l’entrée “point” dans le répertoire).
Les liens durs sont créés avec la création d’un fichier ou d’un répertoire. Cependant il est
possible de créer de liens durs supplémentaires sur les fichiers existants avec la commande
UNIX ln ou avec la fonction link, voir la section 4.3.
4.2
struct stat et les fonctions stat fstat lstat – consultation d’informations d’inode
La structure struct stat est utilisée pour récupérer les informations stockées dans un
inode :
#include <sys/types.h>
#include <sys/stat.h>
struct stat{
dev_t st_dev;
/*identificateur de systeme de fichiers (volume *
19
* logique) contenant le fichier*/
ino_t st_ino;
/*numero de fichier sur le disque
*
* l’identifient de fichier */
mode_t st_mode ; /*type de fichier et droits d’utilisateur*/
nlink_t st_nlink; /*nombre de liens durs*/
uid_t st_uid;
/*proprietaire*/
gid_t st_gid;
/*groupe proprietaire*/
off_t st_size:
/*taille*/
time_t st_atime;
/*date de dernier acces (temps depuis 1.01.1970)*/
time_t st_mtime; /*date de derniere modif des donnees*/
time_t st_ctime; /* derniere modific de caracteristiques*/
}
Pour obtenir ces information on utilise les fonctions suivantes :
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *reference,struct stat *p_stat)
int fstat(int descripteur, struct stat *p_stat)
int lstat(const char *reference, struct stat *p_stat)
Les fonctions stat et lstat prennent comme paramètre le chemin vers un fichier, la
fonction fstat prend un descripteur de fichier ouvert. Le deuxième paramètre de chaque
fonction c’est l’adresse de la structure struct stat, la structure sera mise à jour par
l’appel.
Remarque sur les liens symboliques. La différence entre stat et lstat réside dans
le traitement de liens symboliques, voir la section 8. Pour stat les liens symboliques sont
transparents, c’est-à-dire stat appliquée à un lien symbolique donne les information concernant le fichier pointé par le lien et non les informations sur le lien symbolique lui-même.
Par contre lstat appliquée à le lien symbolique donne les information sur ce lien.
Exemple. Pour recuperer les informations d’inode de fichier /home/dupont/toto on fait :
struct stat bufstat;
stat("/home/dupont/toto", &bufstat);
et maintenant on peut afficher l’identifient du propriétaire :
20
printf("id proprio=%d\n", bufstat.st_uid);
Une fois la structure struct stat initialisée comme indiqué dans l’example ci-dessus
il est possible de vérifier le type de fichier. On utilise les macro-fonctions suivantes qu’on
applique au champ st_mode de la structure struct stat :
macro-fonction
type de fichier
lettre (type) affichée par ls -l
S_ISREG(bufstat.st_mode)
fichier régulier
S_ISFIFO(bufstat.st_mode) fichier spéciale FIFO (tube)
p
S_ISCHR(bufstat.st_mode)
type spécial caractère
c
S_ISBLK(bufstat.st_mode)
type special bloc
b
S_ISDIR(bufstat.st_mode)
type spécial répertoire
d
S_ISLNK(bufstat.st_mode)
lien symbolique
l
S_ISSOCK(bufstat.st_mode)
socket
s
5
Pour connaı̂tre les droits d’accès sur un fichier on utilise aussi le champ st_mode et
les mêmes macro-constantes qu’on utilise pour spécifier les droits d’accès dans open :
S_IRUSR S_IWUSR S_IXUSR
S_IRWXU
S_IRGRP S_IWGRP S_IXGRP
S_IRWXG
S_IROTH S_IWOTH S_IXOTH
S_IRWXO
Donc par exemple
if( bufstat.st_mode & S_IRUSR ){
/* droit r pour le proprietaire */
}
permet de vérifier si le propriétaire de fichier possède le droit de lecture.
Le champ st_mode contient aussi les bits setuid, setgid et sticky bit. Pour vérifier si
c’est bits sont positionnés on utilise les macro-constantes :
S_ISUID set-user-ID (setuid) positionné
S_ISGID set-group-ID (setgid) positionné
S_ISVTX sticky bit positionné
Par exemple :
if ( bufstat.st_mode & S_ISVTX ){
/* sticky bit est positionne */
}
Pour la description de sticky bit voir la section 6.2. Pour la signification de bits setuid et
setgid voir la section 6.
5. Les droits sont vérifiés vis-à-vis de propriétaire effectif du processus, voir les processus.
21
4.3
Création/suppression/changement de nom de lien dur
#include <unistd.h>
int link(const char *origine, const char *cible)
int unlink(const char *reference)
int rename(const char *ancien, const char *nouveau)
4.3.1
Création/renommage de liens durs – link, rename
link link crée un nouveau lien dur (lien physique) sur un fichier. origine est la référence
vers un fichier existant, cible est la nouvelle référence qui sera créée par link.
La cible ne doit pas exister. Origine ne peut être un répertoire (sauf si c’est le super-user
qui exécute link). Origine et cible doivent être impérativement dans le même système de
fichiers (le même volume logique).
link crée juste un nouveau lien dur, il n’y a pas de création de inode.
Exemple. Supposons par exemple que dans la configuration de fichiers de la figure 2 le
processus exécute :
link("Autres/portable", "Loisir/portable_Marc")
et que le répertoire courant est Factures. Le résultat c’est la création d’un nouveau lien
dur dans le répertoire Loisir, c’est lien pointe vers le même fichier que Autres/portable
(le fichier avec st_ino==2006) . La configuration après link est présentée sur la figure 3
page 37.
La commande UNIX qui crée un nouveau lien dur c’est ln, donc si le répertoire courant
de notre terminal est Factures alors
ln Autres/portable Loisir/portable_Marc
aurait le même effet que link
rename rename permet de déplacer et/ou changer le nom de lien dur. On ne peut pas
renommer ni . (dot) ni .. (dot dot) . Si nouveau existe avant link il sera supprimé.
Exemple. En reprenant l’exemple de la figure 2, si le répertoire courant de processus est
Factures alors
rename(‘‘Maison/loyer’’,’’Autre/loyeraregler’’)
crée un nouveau lien dur dans le répertoire Autre, le nom de ce lien sera loyeraregler et
ce lien pointera vers le fichier 245.
Le lien dur loyer dans le répertoire Maison sera supprimé.
Il faut avoir les droits d’écriture sur les deux répertoires Maison et Autre pour que
l’opération puisse réussir.
22
ulink unlink supprime le lien dur. Cela revient à supprimer l’entrée dans le répertoire
qui pointe vers le fichier. Le fichier correspondant est supprimé seulement si deux conditions
sont réunies :
(1) le nombre de liens dur vers le fichier devient nul,
(2) le nombre d’ouvertures du fichier est nul (aucun processus n’a pas de descripteur ouvert
sur le fichier).
Le fichier sera effectivement supprimé quand le système détecte que le compteur de liens
durs dans l’inode de fichier a la valeur 0 et aucun processus n’a pas de descripteur ouvert
sur le fichier.
Il n’y a pas de fonction de suppression de fichier dans POSIX, cependant couramment
on parle de suppression de fichier si on exécute unlink. La même remarque s’applique
d’ailleurs à la commande rm, elle supprime le lien dur, le fichier lui-même sera supprimé
seulement si les conditions ci-dessus sont satisfaites.
Pour supprimer le lien dur vers le fichier avec unlink il n’est
nécessaire ni d’en ^
etre propriétaire ni d’avoir une quelconque
permission sur le fichier.
Par contre il est nécessaire d’avoir la permission d’écrire
dans le répertoire dans lequel on supprime le lien dur.
5
Les descripteurs, les fichiers ouverts, les inodes – vue générale
Chaque processus possède ses propres descripteurs. On peut imaginer que chaque processus possède une table de descripteurs et le descripteur retourné par open c’est l’indice
de cette table.
Le noyau maintient une autre table – la table de fichiers ouverts. Noter que contrairement aux tables de descripteurs, une par processus, il y a une seule table de fichiers ouverts
dans le noyau.
Chaque objet ≪ fichier ouvert ≫ de la table de fichiers ouverts contient les informations
suivantes :
(1) les différents flags spécifiés au moment de l’ouverture de fichier :
O_RDONLY O_WRONLY O_RDWR O_NONBLOCK O_APPEND
(2) la position courante (offset) dans le fichier,
(3) un pointeur vers un objet inode dans la table de inodes,
(4) un compteur de descripteurs qui pointent vers l’objet
23
≪
fichier ouvert ≫.
Donc nous avons le troisième niveau : la table des inodes, il y a une seule table des
inodes dans le noyau. Pour chaque fichier ouvert il y a un seul objet inode dans la table
des inodes, peu importe combien de fois le fichier a été ouvert.
L’objet inode dans la table des inodes contient
(1) des informations indiquées dans la section 4.1,
(2) le compteur des objets
≪
fichier ouvert ≫ qui pointent vers cet inode.
Pour résumé, un objet descripteur dans la table de descripteur pointe vers un objet
fichier ouvert ≫ qui se trouve dans la table de fichiers ouverts et, à son tour, l’objet ≫fichier
ouvert≪ pointe vers un objet inode dans la table de inodes.
≪
Le même processus peut ouvrir plusieurs fois le même fichier :
Exemple. Soit
int d=open("toto’,O_WRONLY);
int e=open("toto",O_RDWR);
Chaque appel open crée un nouveau descripteur dans la table de descripteurs du processus
et il crée un nouveau objet ≪ fichier ouvert ≫ dans la table de fichiers ouverts. Les descripteurs d et e pointent vers deux objets ≪ fichier ouvert ≫ différents dans la table de fichiers
ouverts.
Puisque la position courante est associée à l’objet ≪ fichier ouvert ≫, la position courante
(offset) dans le fichier vue par d n’a rien avoir avec la position courante vue par e. Par
exemple si juste après l’ouverture on fait
char t[] = "abc";
write(d,t,strlen(t));
la position courante vue par d avance de trois octets à cause de write, par contre la position
courante pour e reste toujours au début du fichier.
Les deux objets ≪ fichier ouvert ≫ de la table de fichiers ouverts obtenus précédemment
pointent vers le même objet inode dans la table des inodes.
Mais il est aussi possible qu’un processus possède plusieurs descripteurs qui pointent
vers le même objet ≪ fichier ouvert ≫ dans la table de fichiers ouverts. Cela est possible
grâce au mécanisme de duplication de descripteurs avec les fonctions dup est dup2.
Exemple. Après l’exécution de
int d=open("toto’,O_RDWR);
dup2(d,1);
24
les deux descripteurs, d et 1, pointent vers le même objet
de fichiers ouverts.
≪
fichier ouvert ≫ dans la table
Chaque opération write, read ou lseek sur un descripteur change la position courante
vue par l’autre.
Et finalement deux processus différents peuvent avoir les descripteurs (chacun les siens,
puisque leur tables de descripteurs sont différentes) qui pointent vers le même objet ≪ fichier
ouvert ≫ dans la table de fichiers ouverts. Cela arrive juste après fork où le fils obtient une
copie de la table de descripteurs du père, donc leurs descripteurs pointent vers les mêmes
objets ≪ fichier ouvert ≫ dans la table de fichiers ouverts. Donc une opération read write
ou lseek effectuée par père change la postion courante pour le fils et vice versa.
Ensuite la situation change dynamiquement puisque à partir de ce moment chaque
processus peut effectuer close, open, dup ce qui affecte uniquement sa table de descripteurs.
La figure 4 page 4 donne un aperçu de tables de descripteurs, table de fichiers ouverts
et table de inodes.
5.1
Dernières remarques sur les liens durs
Les premiers systèmes UNIX (SVR3 et 4.1BSD) implémentaient seulement les liens
durs. Le problème : les liens durs ne peuvent pas traverser d’un système de fichiers à
l’autre.
Faire les liens durs vers des répertoires peut former de cycles dans le système de fichiers
et dans ce cas certains fonctions comme find et du peuvent entrer dans une boucle infinie.
Pour cette raison seulement super-utilisateur peut faire les liens durs vers des répertoires.
Comme chacun peut ajouter les liens durs avec link les fichiers ne forment pas un arbre
mais plutôt un graphe acyclique.
Les liens durs provoquent des problèmes de contrôles. Supposons que Jean possède un
fichier /home/Jean/file1 et Michel fait un lien dur /home/Michel/link1 vers ce fichier.
Pour cela Michel a besoin seulement les permissions de passage sur les répertoires qui
mènent vers le fichier et le droit de lecture sur le répertoire /home/Jean.
Supposons que Jean exécute unlink sur /home/Jean/file1 Il peut croire que le fichier
est effectivement supprimé (d’habitude on ne regarde pas le compteur de lien sur nos propres
fichiers). Mais il perd uniquement le lien dur vers le fichier.
Bien sûr Jean reste le propriétaire de fichier mais il ne sait pas que le fichier existe
toujours et si Michel protège la lecture de répertoire /home/Michel Jean n’a aucun moyen
de trouver le lien dur /home/Michel/link1 vers son propre fichier.
25
6
Les bits : set-uid, set-gid — le propriétaire réel et le propriétaire effectif
Le champ st_mode de struct stat contient aussi trois drapeaux (bits) : set-uid set-gid
et sticky.
Pour comprendre à quoi servent ces trois bits il faut comprendre comment les systèmes
d’exploitation determine si un processus peut accéder à un fichier.
6.1
Les bits setuid et setgid
Chaque processus possède deux propriétaires :
(1) le propriétaire réel – c’est celui qui a créé le processus,
(2) le propriétaire effectif – c’est celui que le système utilise pour déterminer si le processus
possède les droits d’accès à un fichier.
Dans la plupart de cas le propriétaires effectif et réel sont les mêmes.
En exécutant
ls -l /bin/cat
on obtient
-rwxr-xr-x 1 root root 46764 Oct
2 05:25 /bin/cat
Donc comme nous pouvons le voir le propriétaire de fichier exécutable /bin/cat est
root.
Supposant que sophie exécute la commande
cat toto.txt
Qui est le propriétaire du processus qui exécute cat, sophie qui lance cat ou root qui
possède le fichier exécutable /bin/cat ?
Dans ce cas le propriétaire réel et le propriétaire effectif du processus cat lancé par
sophie sera sophie elle même, en particulier le fait que root est le propriétaire de fichier
exécutable /bin/cat n’a aucune importance. Ce sont les droits de sophie qui déterminent
si sophie peut ou ne peut pas afficher le contenu du fichier toto.txt avec cat. (C’est
d’ailleurs tout à fait logique parce que l’utilisateur root peut lire n’importe quel fichier et
certainement nous ne voulons pas donner ce privilège à sophie même si nous permettons
à sophie d’exécuter la commande /bin/cat.)
Maintenant regardons la commande passwd qui permet de changer le mot de passe d’un
utilisateur.
ls -l /usr/bin/passwd
26
affiche
-rwsr-xr-x 1 root root 37140 2011-02-14 23:11 /usr/bin/passwd
Nous constatons que root est le propriétaire de ce fichier exécutable mais à la place de x
qui indique le droit de l’exécution pour user (propriétaire de fichier) nous pouvons voir la
lettre s. La lettre s indique que set-uid bit a été positionné pour ce fichier.
Quand sophie lance la commande passwd elle sera le propriétaire réel de processus
exécutant passwd mais le propriétaire effectif de ce processus sera root c’est-à-dire le
propriétaire de fichier exécutable /usr/bin/passwd. Et ce sont les droits de propriétaire
effectif root qui déterminent à quels fichiers peut accéder le processus exécutant passwd.
Et c’est ce qui est nécessaire, passwd accède aux fichiers protégés (appartenant à root)
qui contiennent les mots de passe donc le processus qui modifie le mot de passe doit avoir
les droits de root pour lire et modifier ces fichiers.
En résumé : si le bit setuid est positionné sur un fichier exécutable alors celui qui lance
l’exécution de ce fichier devient le propriétaire réel du processus mais c’est propriétaire de
fichier exécutable qui devient le propriétaire effectif du processus.
Le bit set-gid joue le même rôle que set-uid mais pour le groupe propriétaire.
Le bits set-uid et set-gid sur le fichier non-exécutables n’ont aucun effet.
La constante S_ISUID et la constante S_ISGID permettent de tester si set-uid, set-gid
sont positionnés, voir section 4.2.
Depuis le terminal on peut voir si les bits set-uid ou set-gid sont positionné en regardant
l’affichage produit par la commande ls -l.
Si on exécute ls -l nomfichier alors l’affichage rwsr--r-- (c’est-à-dire s à la place
de x) indique que le droit d’exécution est donné pour le propriétaire et set-uid bit est
positionné. Par contre l’affichage rwSr--r-- (c’est-à-dire S majuscule à la place de x)
indique que set-uid bit est positionné mais le fichier n’est pas exécutable pour le propriétaire.
6.2
Sticky bit
Sticky bit est utilisé pour resteindre les droits de suppression des éléments d’un répertoire.
Le sticky bit est testé avec la constante S_ISVTX :
bufstat.st_mode & S_ISVTX
Si le sticky bit est positionné sur un répertoire alors le fichier dans le répertoire peut être
supprimé ou renommé si l’utilisateur a la permission write sur le répertoire et une des
conditions suivantes est satisfaite :
1. l’utilisateur 6 est le propriétaire du fichier ou
2. l’utilisateur est le propriétaire du répertoire,
6. Plus precisement le propriétaire réel du processus qui essaie d’effectuer l’opération.
27
3. l’utilisateur est super-utilisateur.
Le répertoire /tmp est le candidat typique pour avoir le sticky bit positionné. Les permissions pour ce répertoire sont : read, write, execute pour tous (user, group, other). Mais
l’utilisateur doit être capable de supprimer ou renommer seulement ses propres fichiers
qui se trouvent dans ce répertoire, nous ne voulons pas qu’il soit capable de supprimer ou
renommer les fichiers présents dans /tmp mais qui ne lui appartiennent pas. ls -l pour
/tmp affiche
drwxrwxrwt
14 root root
4096 2011-11-27 20:26 tmp
Le t à la fin de droits d’accès (à la place de x) indique que le sticky bit est positionné
sur répertoire tmp et que le droit de passage x est accordé pour other.
(Si à la place de t on trouve T cela signifie que le sticky bit est positionné mais other
n’a pas de droit de passage sur ce répertoire.)
6.3
Changement de caractéristiques de fichier : droits d’accès, propriétaire,
dates d’accès
Les fonctions
#include <sys/stat.h>
int chmod(const char *reference, mode_t mode)
int fchmod(int descripteur, mode_t mode)
permettent de changer les droits d’accès, les bits set-uid, set-gid et sticky.
Pour changer le propriétaire de fichier le super-utilisateur root utilise
#include <sys/stat.h>
int chown(const char *reference, uid_t uid, gid_t gid)
int fchown(int descripteur, uid_t uid, gid_t gid)
Pour changer manuellement les dates utilisez la fonction :
#include <sys/types.h>
#include <utime.h>
int utime(const char *reference, const struct utimbuf *temps)
struct utimbuf{
time_t actime; /*pour st_atime*/
time_t modtime; /*pour st_mtime*/
}
28
7
Nom d’utilisateur, le répertoire initial, le shell
Les fonctions suivante appliquées soit au numéro identifiant de l’utilisateur soit au nom
d’utilisateur permettent de retrouver les informations le concernant.
#include <pwd.h>
struct passwd *getpwuid(uid_t uid)
struct passwd *getpwnam(const char *nom)
La structure struct passwd dont le pointeur est retourné contient les champs suivants :
char
uid_t
gid_t
char
char
*pw_name
pw_uid
pw_gid
*pw_dir
*pw_shell
User’s login name.
Numerical user ID.
Numerical group ID.
Initial working directory.
Program to use as shell.
En particulier si on retrouve le numéro identifiant du propriétaire d’un fichier à l’aide de
stat l’appel à getpwuid permettra de retrouver ensuite le nom du propriétaire.
8
Liens symboliques
4.2BSD introduit les liens symboliques. Le lien symbolique est un fichier spécial dont le
contenu est un chemin absolu ou relatif vers un autre fichier.
Pour la plupart de fonctions ou commandes le lien symbolique est transparent. Par
exemple stat appliqué à un lien symbolique retourne les caractéristique de inode de fichier
référencé par le lien et non pas les caractéristiques de inode du lien lui-même.
Pour trouver le caractéristiques d’un lien symbolique il faut utiliser la fonction lstat.
Liens symboliques intégrés en POSIX.1 :2001. Dans la commande ls -l les liens symboliques apparaissent avec le type l.
8.1
Création du lien symbolique
Création sous shell avec la commande
ln -s target link_name
qui crée un lien dont le nom est link_name et dont le contenu est target. Cette commande
crée un nouveau inode et un nouveau fichier de type lien symbolique (rappelons que ce
n’est pas le cas pour les liens dur où ln crée juste une nouvelle entrée de répertoire).
29
Exemple. La commande
ln -s /usr/bin monbin
crée un lien symbolique monbin dans le répertoire courant. Le contenu de ce lien est la suite
de caractères /usr/bin (ce n’est pas une chaı̂ne de caractères, il n’y a pas de caractère nul
à la fin).
Si on exécute maintenant
ls -l | grep monbin
on obtient
lrwxrwxrwx
1 zielonka
None
8 Nov 12 16:37 monbin -> /usr/bin
Donc il y a bien un nouveau fichier monbin créé, son type est l (lien symbolique), le contenu
du lien est affiché après ->. Création d’un lien symbolique donne toujours lieu à la création
d’un inode correspondant à ce lien.
Une fois le lien symbolique construit si on exécute sur le terminal
cd monbin
alors /usr/bin devient le répertoire courant. La commande cd suit le lien symbolique et
interprète le contenu de lien comme le chemin et change le répertoire courant en /usr/bin.
Exemple. La commande
ln -s ../bin autrebin
crée un lien symbolique autrebin dans le répertoire courant. Le contenu de ce lien est
../bin.
ls -l | grep toto
affiche
lrwxrwxrwx
1 zielonka
None
8 Nov 12 16:37 autrebin -> ../bin
Une fois le lien symbolique construit si on exécute sur le terminal
cd autrebin
alors ../bin devient le répertoire courant. Bien sûr cet exemple marche correctement si la
référence ../bin est une référence vers un répertoire existant. Sinon nous aurons le message
indiquant que ../bin n’existe pas (no such file or directory).
30
Exemple. ln -s ’;,:titi t+’ ../toto
crée un lien symbolique toto dans le répertoire père du répertoire courant. Le contenue
de ce lien c’est une suite de caractères ;,:titi t+ Cet exemple montre que au moment de
la création de lien symbolique il n’y a pas de vérification si le contenu du lien représente
un chemin valable ou non, nous pouvons créer un lien symbolique dont le contenu est une
chaı̂ne de caractères quelconque. Bien sûr un tel lien symbolique n’est pas très utile.
Depuis un programme C on crée un lien symbolique avec la fonction
#include <unistd.h>
int symlink(const char *reference, const char *lien)
qui crée un lien symbolique lien dont le contenu est reference. La fonction retourne 0 si
OK et −1 sinon.
Exemple. L’appel
symlink("toto/momo", "../exo")
– crée un lien symbolique exo dans le répertoire père du répertoire courant. Le contenu
du lien est la chaı̂ne de caractères toto/momo. Comme pour la commande ln -s il
n’y a aucune vérification si toto/momo correspond à un fichier, à cette étape c’est une
suite de caractères stockée dans le lien,
– une nouvelle entrée nommée exo est ajoutée dans le répertoire père du répertoire
courant (dans le répertoire .. ). Le inode associé à cette entrée c’est le inode du lien
symbolique. Le lien dur depuis le répertoire pointe vers le lien symbolique exo.
Rappelons que le lien dur peut être créé uniquement vers un fichier, par contre nous
pouvons créer un lien symbolique vers le répertoire ou vers un fichier.
Il est impossible de créer un lien dur vers un fichier qui réside dans un autre système de
fichier, par contre nous pouvons créer un lien symbolique vers un fichier ou un répertoire
résidant dans un autre disque logique.
8.2
Consultation des attributs d’un lien symbolique
La fonction stat suit le lien symbolique et récupère les attributs de fichier référencé
par le lien symbolique.
Pour récupérer le inode d’un lien symbolique on utilise la fonction
31
#include <sys/types.h>
#include <sysy/stat.h>
int lstat(const char *reference, struct stat *pstat)
Pour tous les fichiers qui ne sont pas des liens symboliques lstat() donne le même
résultat que stat(). Pour un lien symbolique le champ st_size de la structure struct stat
donne la longueur du contenu du lien symbolique (sans caractères nul à la fin).
Exemple. Par exemple pour le lien symbolique créé dans l’exemple 8.1
struct stat s;
lstat("../exo",
&s);
s.st_size donne le nombre de caractères dans "toto/momo"
En général certaines fonctions suivent les liens symboliques tandis que d’autres non 7 .
Ces différents comportements sont répertoriés dans le tableau suivant :
7. Une fonction suit un lien symbolique si, quand elle est appliquée à un lien symbolique, elle agit plutôt
sur le fichier référencé par le lien. Si la fonction ne suit pas le lien symbolique alors appliquée sur un lien
symbolique elle agit sur le lien lui-même.
32
fonction
access
chdir
chmod
chown
creat
exec
lchown
link
lstat
open
opendir
pathconf
readlink
remove
rename
stat
truncate
unlink
8.3
ne suit pas
le lien symbolique
suit le lien
symbolique
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
x
La lecture du lien symbolique
#include <unistd.h>
ssize_h readlink(const char *lien, char *tampon, size_t t)
lit le contenu d’un lien symbolique (sa valeur) et le met dans tampon de taille t. La suite
de caractères copiés dans tampon n’est pas suivie par le caractère nul. Si la taille de tampon
t n’est pas suffisante alors le contenu du lien est tronqué (seulement t premiers caractères
sont copiés). La fonction retourne le nombre de caractères mis dans tampon.
Exemple. Pour le lien crée dans l’exemple 8.1
int i;
char tampon[100];
i=readlink("../exo",&tampon,99);
tampon[i]=’\0’;
33
va copier dans tampon les caractères "toto/momo" (sans caractère nul à la fin). Pour obtenir
une vraie chaı̂ne de caractères avec nul à la fin on a ajouté
tampon[i]=’\0’;
Par contre
int i;
char tampon[3];
i=readlink("../exo",&tampon,3);
va copier dans tampon les trois premiers caractères de la chaı̂ne "toto/momo$" (encore une
fois sans caractère nul à la fin).
La question se pose comment savoir qu’elle est la taille de tampon à préparer pour lire
le contenu de lien symbolique. La solution passe par la lecture de caractèristiques de inode
avec lstat, le champs st_size de la structure struct stat nous donnera la longueur (en
octets) de contenu du lien :
char *tamp;
strcut stat b;
int i;
lstat("../exo", &b);
tamp = (char *)malloc(b->st_size+1);
i=readlink("../exo",tamp,b->st_size);
tamp[i]=’\0’;
8.4
Modifications des propriétés de lien symbolique
int lchmod(const char *reference, mode_t mode)
int lchown(const char *reference, uid_t uid, gid_t gid)
permettent de modifier le droit d’accès et le propriétaire de lien symbolique.
9
Lecture/écriture non bloquantes
#include <sys/types.h>
#include <unistd.h>
34
#include <fcntl.h>
int fcntl(int descripteur, int commande, ...)
int m=fcntl(desc, GET_FL)
Cet appel retourne l’état de la description de fichier ouvert, en particulier le drapeaux
O_APPEND et O_NONBLOCK. La valeur retournée contient également le mode d’ouverture de
fichier (lecture ou écriture).
fcntl(desc, F_SETFL, mode)
modifie l’état de description en fonction du paramètre mode. Les valeurs possibles de mode
O_APPEND O_NONBLOCK, O_APPEND|O_NONBLOCK et 0.
Pour basculer vers le mode non bloquant exécutez :
int mode = fcntl(desc,F_GETFL);
fcntl(desc, F_SETFL, mode|O_NONBLOCK);
L’inverse, pour passer au mode bloquant :
int mode = fcntl(desc, F_GETFL);
fcntl(desc, F_SETFL, mode & ~O_NONBLOCK);
Nous allons utiliser la lecture non bloquante surtout avec les tubes. Mais l’entrée standard (descripteur 0) est aussi ouverte en mode bloquant, si un processus fait
read(0, buffer, sizeof buffer);
alors il est bloqué tant que l’utilisateur ne lui renvoie pas une ligne tapée sur le terminal.
Nous pouvons passer en mode non bloquant sur l’entrée standard de façon indiquée
précédemment.
En général, si O_NONBLOCK est activé et il n’y a pas de donnés à lire alors read()
retourne −1 et errno prend la valeur EAGAIN.
Si O_NONBLOCK n’est pas activé et il n’y a pas de donnés à lire alors read() et bloqué
en attente de donnés.
35
père de Factures
?
154
..
Factures
..
.
Loisirs
4
198 Maison
Autres
3
205
..
.
Enfants
portable
..
.
gaz
loyer
..
.
cine
2
278
2
244
1
206
..
.
2
207
1
245
1
252
1
302
Figure 2 – Le même sous-arbre que dans la figure 2 mais avec le contenu des répertoires.
De plus le répertoire père de Factures est aussi visible. Les rectangles jaunes représentent
les inodes. Les seules informations affichées dans les inodes dans cette figures ce sont :
(1) le nombre de liens durs vers le fichier et (2) le numéro identifiant de fichier. D’autres
informations contenues dans les inodes ne sont pas représentées ici. A noter que le nom de
fichier c’est juste le nom que lui donne son père, le fichier lui-même (son inode) connaı̂t
uniquement son identifiant numérique.
36
père de Factures
?
154
..
Factures
..
.
Loisirs
4
198 Maison
Autres
3
205
..
.
Enfants
portable
..
.
gaz
loyer
..
.
cine
2
244
2
206
..
.
2
207
1
245
2
278
1
252
1
302
Figure 3 – L’appel link(Autres/portable, Loisir/portable Marc) crée un nouveau
lien dur vers le fichier 206. Noter qu’il n’y a pas de création de fichier, juste une nouvelle
entrée dans le répertoire Loisir. Le compteur de lien physiques du fichier 206 passe de 1
à 2.
37
processus 1
tables des
descripteurs
(une table par processus)
table de fichiers ouverts
table de inodes
en m
re
2
2
processus 2
1
2
2
1
processus 3
le nombre de références
vers inode
3
1
0
compteur de mode
descripteursd'ouverture
référence sur inode
offset
Figure 4 – Chaque de trois processus possède sa propre table de descripteurs. Au total il
y a trois fichiers ouverts mais deux parmi ces fichiers ont été ouverts deux fois ce qui donne
5 objets ≪ fichier ouvert ≫ dans la table de fichiers ouverts.
38

Documents pareils