Vue d'ensemble de l'architecture de l'espace utilisateur

Manux a sa propre architecture de l'espace utilisateur, qui ne ressemble à aucune autre. Essayons de la décrire.

Organisation sur le disque dur

Tout d'abord, étudions la disposition des fichiers sur le disque dur. Pour comparaison, voici l'aspect de la racine d'une distribution Linux classique :

ecolbus@linux:~$ ls -F /
bin/    etc/             lib/         opt/   sbin/     tmp/      vmlinuz.old@
boot/   home/            lost+found/  proc/  selinux/  usr/
cdrom/  initrd.img@      media/       root/  srv/      var/
dev/    initrd.img.old@  mnt/         run/   sys/      vmlinuz@

Montons à présent la partition racine d'un système Manux, et voyons ce qu'elle contient :

root@linux:~# mount /mnt/manux-ro 
root@linux:~# ls -F /mnt/manux-ro/
lost+found/  packages/

Comme vous vous en rendez sans doute compte, ce n'est pas exactement ainsi que la racine d'un système UNIX classique est sensée apparaître.

Décrivons rapidement ces éléments. "lost+found" est un répertoire lié au système de fichiers qui doit se trouver dans sa racine. "packages", qui correspondra effectivement à "/packages" lorsque le système fonctionnera, est le répertoire qui contient tous ses paquetages; toutefois, sous Manux, tous les fichiers font partie d'un paquetage. Donc, oui, "/packages" contient l'ensemble du système d'exploitation. Enfin, "/" lui-même est un répertoire spécial, toujours inatteignable (sauf si vous décidez de monter la partition ailleurs), qui sert à certains algorithmes du système de fichiers virtuel (module noyau : sfv).

Bien. À présent, qu'y a-t-il dans "/packages" ?

root@linux:~# ls -F /mnt/manux-ro/packages/
core/  local/  std/

Ici, nous n'avons que 3 répertoires, parce que nous sommes dans la version 0.0.1. Chacun correspond à une distribution, ou un distributeur (par exemple, si le roi Arthur décidait d'empaqueter et de distribuer son logiciel de recherche de Graal, il irait dans "/packages/table_ronde"), à l'exception de "local", qui contient les paquetages spécifiques aux utilisateurs de la machine, comme leurs répertoires personnels.

En ce qui concerne les deux autres, "core" contient le coeur du système d'exploitation, tandis que "std" contient la distribution initiale, avec des composants si communs (comme la glibc ou bash) qu'ils seront vraisemblablement réutilisés sans modifications par d'autres distributions.

Organisation des paquetages

Mais qu'y a-t-il dans ces répertoires? Essayons quelques commandes.

root@linux:~# ls -F /mnt/manux-ro/packages/std/0.0.1/vim/7.3
alias/  etc/  misc/  userdirs/  vim/
root@linux:~# ls -F /mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim
lnc/  lncb/  meta/  root/  userdirs

Là, les choses commencent à devenir intéressantes. Notons tout d'abord que tous les paquetages sont situés dans des arborescences organisées de la même façon dans /packages : "distrib/version_distrib/logiciel/version_logiciel/portion_logiciel". A cet emplacement se trouvent deux répertoires obligatoires, "root" et "meta", respectivement le contenu et les métadonnées du paquetage, ainsi que deux répertoires optionnels, "lncb" et "lnc", qui sont liés à la façon de lancer les éventuels exécutables du paquetage. Accessoirement, nous notons un curieux fichier nommé "userdirs" :

root@linux:~# stat /mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/userdirs
  Fichier : "/mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/userdirs"
   Taille : 4096      	Blocs : 8          ES blocs : 4096   fichier étrange
Device : 807h/2055d	Inode : 195906      Liens : 2
Accès : (0777/?rwxrwxrwx)  UID : (    0/    root)   GID : (    0/    root)
Accès : 2013-07-06 15:34:38.000000000 +0200
Modi. : 2013-07-06 15:34:38.000000000 +0200
Chgt  : 2013-07-06 15:34:38.000000000 +0200
Créé  : - 

Il s'agit en fait d'un lien matériel vers un répertoire, mais nous verrons cela plus loin.

Quel est l'aspect des choses dans "root"? Voyons, voyons :

root@linux:~# ls -F /mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/root/
etc/  lib/  tmp/  usr/

Ah, ben ça, ça ressemble à une racine standard!

Sous Manux, tous les programmes de l'espace utilisateur sont chrootés, et leurs paquetages leurs servent de racines. Cela dit, mentionnons immédiatement que ce répertoire n'a ce contenu que parce que nous observons un système déjà installé. Dans le répertoire "root", ce paquetage ne fournit en fait que le contenu de /usr/bin :

root@linux:~# ls -F /mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/root/usr/bin/
vim*

(Bon, pour être exact, il fournit aussi un fichier vide /etc/ld.so.cache, mais c'est sans importance.)

Qu'en est-il de "meta"?

root@linux:~# ls -F /mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/meta/
crypto/  deps  files  identity  install*  origins  userdirs

Il s'agit des données requises par le système de paquetages, dont la description sera le sujet d'un autre document.

Lanceurs

Et "lncb" et "lnc"?

root@linux:~# ls -l /mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/lnc{b,}
/mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/lnc:
total 16
-rwxr-xr-t 2 root root 12784 juil.  6 15:34 vim

/mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/lncb:
total 4
-rw-r--r-- 1 root root 390 janv.  1  1970 vim.lncb

Comme précédemment mentionné, ces répertoires sont liés aux exacutables, donc dans le paquetage de vim, il n'est pas surprenant de ne trouver que des fichiers liés à vim. Le répertoire "lncb" contient un fichier fourni par le paquetage spécifiant comment lancer vim ("lnc" signifie "lanceur", le "b" indique le format binaire non-exécutable), tandis que "lnc" contient les véritables lanceurs créés à partir des fichiers lncb durant l'installation du paquetage. (Pourquoi faire si compliqué, demandez-vous? Et bien, nous devons gérer le cas où l'empaqueteur est en fait hostile. Le format lncb est très limité, mais il garantit que les lanceurs créés à partir de lui demeureront inoffensifs).

Mais intéressons-nous au fichier "lnc/vim". Il s'agit d'un exécutable :

root@linux:~# file /mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/lnc/vim 
/mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim/lnc/vim: sticky ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped

Mais il est bien plus petit que le vrai exécutable de vim. De plus, il est lié statiquement, il a deux liens matériels, et son bit sticky est activé. Qu'est-ce que cela signifie?

Comme nous l'avons dit, la tâche de ce fichier est de lancer vim lorsqu'il est appelé. Dans la mesure où tous les processus sont chrootés, si l'utilisateur appelle vim, son shell va devoir appeler ce lanceur depuis son chroot. C'est possible parce que ce lanceur est lié matériellement dans la racine de l'utilisateur - d'où le second lien matériel. Si d'autres applications avaient une dépendance sur vim, ou s'il y avait plus d'utilisateurs sur cette machine, il y aurait plus de liens matériels, mais ce n'est pas le cas.

Mais il y a un problème : quelle bibliothèque C est disponible dans le chroot de l'appelant? Réponse : nous ne pouvons pas le savoir à l'avance, aucune n'est garantie, et en plus, le paquetage appelant pourrait être hostile, donc il n'est pas possible de dépendre de ses bibliothèques. C'est pourquoi tous les lanceurs sont liés statiquement.

Finalement, comment ce binaire est-il sensé trouver le vrai binaire de vim lorsqu'il est appelé depuis un chroot externe arbitraire? C'est ici que le bit sticky entre en jeu.

Le bit sticky

La signification du bit sticky sur des fichiers réguliers n'est pas spécifiée, et en pratique, il n'est pas utilisé; j'ai donc décidé de le réutiliser dans mon système de fichiers. Dans ext2l, le bit sticky sur un fichier régulier informe l'utilisateur que ce fichier a un lien racine, un nouveau type de lien, semblable à un lien matériel, qui associe asymmétriquement un fichier régulier à un répertoire. Seul un processus exécutant ce fichier régulier peut en faire usage, et lorsqu'il le fait, son chroot change, et le répertoire visé devient sa nouvelle racine (et son nouveau répertoire courant). Dans le cas habituel, le répertoire visé est le "/root" du paquetage, mais il se trouve que vim est une exception - nous verrons pourquoi plus tard. Pour l'instant, voyons ce qui se passe lorsque quelqu'un appelle un programme plus standard, /bin/cat :

  1. L'application (dans ce cas l'interpréteur de commandes de l'utilisateur) appelle classiquement fork(2) et cherche cat dans son PATH; elle le trouve dans /bin/cat ;
  2. L'application appelle execve(2) sur l'exécutable trouvé dans /bin/cat; sans savoir qu'il ne s'agit en réalité que du lanceur de cat;
  3. Le lanceur effectue quelques analyses, puis appelle xchroot(2) avec le témoin XCHROOT_USE_ROOTLINK;
  4. Le noyau honore l'appel et déplace ce processus depuis le chroot de l'appelant jusque dans le répertoire "/root" dans le paquetage de cat (à la fois comme nouvelle racine et comme répertoire courant, car xchroot(2) implique un chdir(2));
  5. Le lanceur appelle execve(2) sur le vrai binaire de cat.

Ainsi, grâce au lien racine, une application est parvenue à en appeler une autre sans qu'aucune n'aie jamais eu accès au chroot de l'autre.

Les userdirs

Ne finissons pas cette discussion sans exmpliquer la spécificité de vim. Ce qui sépare vim de cat, techniquement, c'est que vim comporte des fichiers de configuration (~/.vim*). Les gérer requiert une procédure bien plus complexe.

Tout d'abord, précisons le terme. Un userdir est un répertoire contenant des fichiers spécifiques à une combinaison {utilisateur, version d'une application}. Les systèmes d'exploitation classiques stockent ces informations dans le répertoire personnel de l'utilisateur, mais cette approche simple ne fonctionnerait pas sous Manux, dans la mesure où les programmes n'ont pas accès à ce répertoire. D'où la nécessité d'un emplacement spécial.

Observons à nouveau nos commandes initiales :

root@linux:~# ls -F /mnt/manux-ro/packages/std/0.0.1/vim/7.3
alias/  etc/  misc/  userdirs/  vim/
root@linux:~# ls -F /mnt/manux-ro/packages/std/0.0.1/vim/7.3/vim
lnc/  lncb/  meta/  root/  userdirs

Ah, oui, voilà l'userdir. Mais pourquoi y en a-t-il deux?

Souvenez-vous que la première commande n'a fait que lister les paquetages présents dans /packages/std/0.0.1/vim/7.3 . Donc le premier est un paquetage indépendant - sa raison d'être est de permettre à l'empaqueteur de fournir une configuration par défaut, et de stocker ces userdirs.

Et le second? Souvenez-vous de ce qui a été dit à son sujet plus haut : il s'agit d'un lien matériel vers un répertoire, plus précisément vers le répertoire "root" du premier.

Pourquoi faire si compliqué, demandez-vous? Et bien, tout d'abord, rien ne spécifie que vim 7.3 est tenu d'employer les userdirs de vim 7.3 - si la configuration était restée compatible, et si un tel paquetage existait, il pourrait utiliser les userdirs de vim 7.2 (la façon dont le userdir est trouvé relève du système de paquetage). Donc, le premier n'est pas réellement utilisable.

De plus, il se trouve que, lorsqu'un logiciel a des userdirs, ses lanceurs ont un lien racine pointant vers le père immédiat de leur répertoire "/root". Mais à présent, les explications théoriques deviennent un peu compliquées, donc voyons en pratique les opérations qui ont lieu lorsque quelqu'un lance vim :

  1. L'application (dans ce cas l'interpréteur de commandes de l'utilisateur) appelle classiquement fork(2) et cherche vim dans son PATH; elle le trouve dans /usr/bin/vim ;
  2. L'application appelle execve(2) sur l'exécutable trouvé dans /usr/bin/vim; sans savoir qu'il ne s'agit en réalité que du lanceur de vim;
  3. Le lanceur effectue quelques analyses, puis appelle xchroot(2) avec le témoin XCHROOT_USE_ROOTLINK;
  4. Le noyau honore l'appel et déplace ce processus depuis le chroot de l'appelant jusque dans le répertoire "/" du paquetage de vim (c'est-à-dire dans /packages/std/0.0.1/vim/7.3/vim, au lieu de /packages/std/0.0.1/vim/7.3/vim/root si vim avait été un programme classique);
  5. Le lanceur appelle getuid(2), pour identifier son utilisateur;
  6. Ensuite, il cherche le répertoire /userdirs/<uid> ;
  7. S'il n'existe pas, le lanceur appelle fork(2), puis exécute /userdirs/bin/mkuserdir, pour le construire; à son tour, mkuserdir copie le modèle stocké dans /userdirs/0;
  8. Après quoi, le lanceur modifie son environment de sorte que la variable HOME pointe ce répertoire, et effectue des analyses additionnelles;
  9. Puis, il appelle xchroot(2) sans XCHROOT_USE_ROOTLINK, et avec une nouvelle racine dans /root;
  10. Et, finalement, il appelle execve(2) sur le vrai binaire de vim.

De la sorte, la variable d'environnement HOME a été modifiée afin que vim y trouve les fichiers de configuration de l'utilisateur sans pouvoir accéder à la configuration d'autres personnes. Ce choix architectural implique que la variable HOME peut changer sans que l'utilisateur n'en aie le contrôle; c'est un prix à payer pour la sécurité du système d'exploitation.

La partition trois-points

Ha! Vous pensiez réellement que nous avions fini? Regardez bien les exemples précédents, vous ne remarquez pas qu'il reste un problème?

J'explique. Si l'utilisateur décide de lancer "cat", tout ira bien, en effet. Mais que se passera-t-il s'il tape "cat mon_fichier.txt"?

Oui, regardez attentivement. Le lanceur va se chrooter, puis appeler cat, qui va chercher un fichier nommé "mon_fichier.txt" dans son répertoire... Mais il a été chrooté (et sous Manux, xchroot(2) implique chdir(2)), donc le fichier ne s'y trouvera pas. Comment est-ce sensé fonctionner?

Je vais l'expliquer, mais une remarque avant tout : cela ne peut pas être montré sur un système Manux arrêté. Démarrons-le donc, tapons notre login/mot de passe, et jetons un oeil à notre répertoire personnel.

ecolbus@manux:~$ pwd
/.../home/ecolbus

Ah! Il y a quelque chose de nouveau, ici! Ce n'est pas /home/ecolbus, mais /.../home/ecolbus . Bon, essayons d'autres commandes...

ecolbus@manux:~$ touch file
ecolbus@manux:~$ ls -l file
-rw-r--r-- 1 ecolbus ecolbus 0 20 juil. 01:33 file
ecolbus@manux:~$ ls -l /.../home/ecolbus/file
-rw-r--r-- 1 ecolbus ecolbus 0 20 juil. 01:33 /.../home/ecolbus/file

Jusqu'à présent, tout va bien...

ecolbus@manux:~$ ls -l /bin/cat
-rwxr-xr-t 4 sys sys 5264 13 juil. 15:05 /.../??/cat

Euh, quoi? Le nom du fichier a changé?

Oui, il a changé. À présent, place aux explications.

/... est un système de fichiers spécial, sans existence réelle, comme /proc (son module noyau est p3p). A la différence de /proc, /... peut toujours être atteint, quel que soit le chroot. De plus, son contenu dépend du processus qui le regarde, et le fait de savoir s'il est visible ou non dans / n'est pas spécifié (actuellement, il ne l'est jamais).

Ce système de fichiers ne peut contenir que des répertoires (et des liens symboliques), mais il est possible de lui demander d'établir des associations entre ses entrées de répertoire et des fichiers "réels" (voyez xchroot(2)). Par exemple, on peut lui demander "fais correspondre /.../abcd/efgh à mon fichier /etc/hostname", et le nouveau chroot contiendra une entrée /.../abcd/efgh correspondant au précédent /etc/hostname.

Le rapport avec notre affaire? Et bien, comme je l'ai dit, il y a un problème si quelqu'un tente de taper "cat mon_fichier.txt". Vous vous souvenez lorsque j'ai dit que le lanceur "effectuait diverses analyses"? C'est là son autre fonction : il analyse la ligne de commande, séparant ce qui est un fichier de ce qui n'en est pas un, et passe tous les fichiers nécessaires dans le chroot de la nouvelle commande, dans son répertoire /... .

(Pour des raisons techniques, cela doit avoir lieu dans le même appel-système que le chroot, c'est donc à nouveau le travail de xchroot(2). Cette fois, au lieu des témoins, ce sont les entrées qui sont utilisées.)

Bon, c'est très joli, mais lorsqu'il a déterminé quels étaient les fichiers à passer, comment le lanceur choisit-il leurs futurs chemin dans /... ?

Réponse : à chaque fois qu'il peut conserver l'ancien chemin, il le fait; lorsqu'il ne le peut pas ou qu'il n'en est pas certain, il choisit un chemin se terminant par le même nom que le précédent et dont il est certain qu'il n'entrera pas en collision avec un autre.

(Je n'entrerai pas ici dans le détail des techniques employées pour garantir cette unitité (bien que ce soit peut-être l'algorithme le plus intéressant du système d'exploitation, une diagonalisation de Cantor sur 7 bits par octets, voyez le code source si cela vous intéresse); mais mentionnons une évidence : il ne peut pas y avoir de /... dans la racine du paquetage originel (les noms de fichiers points-seuls sont réservés dans ext2l), donc nous sommes sûrs que tout ce que lanceur a à faire, c'est d'éviter toute collision entre les chemins qu'il crée.)

C'est pour cela que le répertoire personnel de l'utilisateur a été délibérément placé dans /... : de la sorte, nous sommes certain que tous ses fichiers peuvent être passés à n'importe quel programme sans avoir à réécrire leur chemin. L'utilisateur les voir déjà dans /... , donc le lanceur n'a qu'à garder ces noms.

De façon plus détaillée, voici ce qui se produit lorsqu'un utilisateur tape "cat mon_fichier.txt" :

  1. L'application (dans ce cas l'interpréteur de commandes de l'utilisateur) appelle classiquement fork(2) et cherche cat dans son PATH; elle le trouve dans /bin/cat ;
  2. L'application appelle execve(2) sur l'exécutable trouvé dans /bin/cat; sans savoir qu'il ne s'agit en réalité que du lanceur de cat;
  3. Le lanceur analyse la ligne de commande, et realise que mon_fichier.txt correspond à un fichier;
  4. Il regarde ensuite le répertoire courant, et remarque que c'est /.../home/username, clairement dans /... ; il en déduit qu'il peut sans danger passer le fichier sous le nom /.../home/username/mon_fichier.txt ;
  5. Il construit l'entrée de xchroot requise pour cela, puis appelle xchroot(2) avec le témoin XCHROOT_USE_ROOTLINK;
  6. Le noyau honore l'appel et déplace ce processus depuis le chroot de l'appelant jusque dans le répertoire "/root" dans le paquetage de cat, simultanément, il crée une série d'entrées dans le /... de ce processus, nommées /.../home, /.../home/username, et /.../home/username/mon_fichier.txt, cette dernière associée au précédent fichier mon_fichier.txt;
  7. Le lanceur déplace son répertoire courant dans la copie du précédent répertoire courant, à savoir /.../home/username;
  8. Le lanceur appelle execve(2) sur le vrai binaire de cat;
  9. cat analyse sa ligne de commande, remarque qu'il est sensé ouvrir le fichier mon_fichier.txt dans le répertoire courant, tente de le faire, y parvient, et effectue sa tâche.

Fichiers hors de /...

Mais qu'en est-il des fichiers situés hors de /... ? Et bien, cette commande nous en a fourni un bon exemple :

ecolbus@manux:~$ ls -l /bin/cat
-rwxr-xr-t 4 sys sys 5264 13 juil. 15:05 /.../??/cat

Dans ce cas, J'ai demandé à /bin/ls d'opérer sur /bin/cat, qui est situé dans ma racine, pas dans mon /... . Le lanceur de ls a repéré le problème, mais n'avait qu'une seule option : choisir un nouveau chemin de fichier, situé dans /... , de sorte que /bin/ls puisse l'accéder depuis son chroot. Son algorithme lui a donné un nom composé de deux caractères illisibles (que ls a remplacés par des '?'), et, après l'appel à xchroot(2), le lanceur a modifié la ligne de commande de ls afin qu'il opère sur le nouveau chemin, et non sur l'ancien.

Il se trouve que "ls" est l'une des rares commandes qui nous permette d'observer ce mécanisme. Cela signifie aussi que Manux se comporte de façon légèrement différente des systèmes d'exploitation traditionnels, et que des incompatibilités peuvent en résulter. C'est un fait indéniable, mais ce n'est qu'une conséquence de sa sécurité - une autre partie du prix à payer pour l'obtenir.

Conclusion

Ceci conclut notre revue d'ensemble de l'architecture de l'espace utilisateur Manux. On pourrait encore dire bien des choses à son sujet, mais il s'agit de principalement de connaissances arcanes (comme la façon dont start_session fonctionne, ou la méthode d'auto-installation du système), qui sont à la fois nettement moins utiles et simplement basées sur les principes exposés ici. Parvenus à ce point, si vous désirez approfondir vos connaissances à son sujet, la meilleure méthode est encore de l'essayer.

Index de la documentation
Page principale