Architecture des pkg_tools d'OpenBSD et outils connexes

GNU/Linux Magazine HS n° 074 | septembre 2014 | Landry (gaston) Breuil
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Derrière ce titre se cache beaucoup de magie. En effet, autour des commandes de base utilisées pour manipuler les paquets sous OpenBSD (telles que pkg_add, pkg_info, pkg_delete...) se trouve un ensemble d'outils pour chercher, vérifier, récupérer, signer, et que sais-je encore faire avec les paquets, ainsi qu'une API Perl. Oui, les pkg_tools sont écrits en Perl. Pas en Python, ni en Ruby, ni en Go, ni en Fortran, ni en Scala, ni en Swift, ni en Dart.... Le bon vieux Perl qui fait chaud aux yeux quand on lit son code.

Par quelques exemples concrets, je vais essayer de vous expliquer les secrets des paquets OpenBSD, montrer quelques usages originaux de tous ces outils/API méconnus, voire montrer des utilisations pratiques pour l'utilisateur final que vous êtes !

1. Les paquets OpenBSD

Petit préambule sur ce qu'est un paquet OpenBSD : ce n'est rien d'autre qu'une archive .tar.gz contenant :

- un fichier +CONTENTS : c'est la packing-list correspondant à la vision « machine » du contenu d'un paquet, stockée (une fois le paquet installé) dans le fichier /var/db/pkg/package-name/+CONTENTS et accessible aussi via pkg_info -f package-name ;

- un fichier +DESC : c'est la description proprement dite du contenu du paquet, affichée via pkg_info -l package-name ;

- enfin, la liste des fichiers proprement dits du paquet, à installer (en général) dans l'arborescence /usr/local.

Les pkg_tools sont en évolution constante depuis leur création il y a une dizaine d'années. Les derniers changements majeurs (et visibles des utilisateurs) ont été :

- L'ajout des README, permettant à un paquet d'installer un fichier dans /usr/local/share/doc/pkg-readmes/ contenant de la documentation spécifique à l'utilisation d'un paquet sous OpenBSD. Par exemple, comment configurer proprement PostgreSQL, ou des astuces pour le démarrage d'Xfce... ;

- L'ajout des scripts rc.d(8) pour 4.9, permettant de démarrer et gérer les démons installés par les paquets. Auparavant, tout se faisait via /etc/rc.local en ajoutant à la main le bout de script shell qui allait bien pour lancer le démon que l'on voulait ;

- La mise en place des signatures de paquets pour 5.5 : la packing-list est maintenant signée par la clef privée du projet OpenBSD, assurant ainsi l'utilisateur que les fichiers qui sont installés sur sa machine sont bien ceux qui ont été compilés sur l'infrastructure du projet. Le code pour gérer des paquets signés existait depuis longtemps dans les pkg_tools et permettait d'utiliser des certificats X.509, mais l'infrastructure de signature n'était pas encore en place, car X.509 était considéré trop compliqué. Un nouvel outil pour gérer simplement des clefs de signature publique/privée a donc vu le jour, et se nomme signify. La signature proprement dite se compose de 2 annotations (documentées dans la page de manuel de pkg_create(1)) ajoutées à la packing-list, @signer correspondant au nom de la clef ayant signé la packing-list, et @digital-signature donnant le type de signature, la date à laquelle le paquet a été signé, et la signature proprement dite :

@signer openbsd-55-pkg
@digital-signature signify:2014-07-6T15:31:54Z:RWQQC1M9dhm/tiTat7ADvDnB087rdQNwLJgo+OKm9ms8bPdnXuHWBz/It/5mwfIk8BoEcfYIY2SDXf9eXf70I0LHPEKOsTy05wA=

Ainsi, l'ensemble des informations contenues dans la packing-list correspond au message à signer en utilisant la clef privée du projet, et la signature est ajoutée ensuite à la packing-list. À l'arrivée sur la machine cliente, pkg_add va lire le paquet, en extraire la packing-list, en extraire la signature, et vérifier (avec la clef publique située sur la machine cliente) que la signature du reste de la packing-list correspond bien à celle qui avait été lue.

En pratique, et pour décortiquer, voici ce qui se passe (en gros) pour vérifier manuellement la signature d'une packing-list d'un paquet installé sur le système :

- On supprime l'annotation @url (elle est ajoutée à la fin de l'installation dans la packing-list stockée dans la PKG_DBDIR, donc non présente dans la packing-list du paquet sur le miroir) ;

- On strippe la signature de la ligne @digital-signature, et on la stocke dans un fichier temporaire avec un en-tête bidon, ce dernier étant juste là pour faire plaisir à signify ;

- On vérifie que la plist (dont on a extrait la signature) récupérée correspond à celle qui a été signée par la clef publique :

$ echo 'untrusted comment: pinpin rocks!' > plist.sig
$ awk -F: '/@digital-signature/ { print $5 }' \
 /var/db/pkg/libvpx-1.3.0/+CONTENTS >> plist.sig
$ sed -e 's/\(@digital-signature.*\):.*/\1/ ; /^@url.*$/d' \
 /var/db/pkg/libvpx-1.3.0/+CONTENTS > plist
$ signify -V -p /etc/signify/openbsd-55-pkg.pub -m plist -x plist.sig
Signature Verified

- Enfin, pour 5.6, une réorganisation de la structure interne des paquets est en cours pour minimiser le nombre de fichiers transférés lors d'une mise à jour, et diviser au moins par 2 la durée globale de la mise à jour.

En effet, si on met les fichiers ayant changé entre 2 compilations du même port (car on garde sur l'architecture de build un historique des checksums de chacun des fichiers mis dans un paquet) au début de l'archive (par exemple souvent les binaires, les librairies...) et les fichiers « statiques » (généralement des images, des textes, documents...) après, lors d'une mise à jour, pkg_add(1) va tout d'abord récupérer la packing-list (qui est le premier fichier dans l'archive), comparer les checksums des fichiers existants de l'ancienne version du paquet avec ceux de l'archive en cours, récupérer les nouveaux fichiers, et s'arrêter dès que l'on sait que les fichiers restants sont les mêmes que ceux déjà sur le système de fichiers. Ainsi, on n'a pas besoin de télécharger l'ensemble du paquet, on prend uniquement les fichiers ayant changé, et comme ils ont maintenant le bon goût d'être au début de l'archive, on économise de la bande passante et on tue moins de chatons.

Ça y est, je vous ai déjà perdu avec toute cette magie ? Pas de souci, on va se faire un petit échauffement avec des concepts simples.

2. Échauffement avec PKG_PATH

pkg_add(1) utilise la variable d'environnement PKG_PATH pour savoir quel miroir contacter pour récupérer des paquets, et si cette variable n'est pas définie, il va chercher la clef installpath dans /etc/pkg.conf. Ce que peu de gens savent, c'est que cette variable peut contenir beaucoup de choses. En effet, elle peut contenir une liste de dépôts, auquel cas le premier dépôt ayant un paquet correspondant à la demande sera utilisé. On peut utiliser différents schémas d'URL : les plus classiques, tels que ftp:// et http://, mais aussi un chemin local si on veut privilégier par exemple /usr/ports/packages/${ARCH}/all par rapport à un miroir, et enfin (et surtout !) une URL scp://, permettant d'installer des paquets de manière sécurisée à travers un transport SSH, qui demande un mot de passe à la volée. Si on ajoute par-dessus une infrastructure de distribution de clefs et un agent SSH, on peut très bien envisager d'installer tous ses paquets via SSH sans interaction.

Petit détail : il faut bien penser à passer le login lors de la connexion SSH, sinon pkg_add(1) essaiera de se connecter en root. Il faut également utiliser le hostname complet, car à cause de sudo, SSH n'ira pas chercher de configuration/alias dans le .ssh/config de l'utilisateur faisant ledit sudo.

Ainsi, PKG_PATH=scp://user@machine.fqdn/path/vers/le/repertoire sudo pkg_add paquet marchera, et utilisera si possible la clé trouvée dans ~user/.ssh pour se connecter (mais n'ira PAS lire le .ssh/config) et PKG_PATH=scp://machine/usr/ports/packages/amd64/all pkg_add -n paquet marchera si ~user/.ssh/config contient un raccourci faisant pointer le Host machine vers le Hostname machine.fqdn. Bon, par contre, avec -n ça ne fait que simuler... Il n'y a pas non plus de possibilité dans l'immédiat de passer des options spécifiques au transport SSH, un peu à la manière de RSYNC_RSH pour rsync.

Il faut noter que pkg_add(1) va vraiment s'arrêter au premier dépôt contenant un paquet correspondant à la demande, et s'il échoue à l'installer pour une raison ou une autre, il n'essaie pas le dépôt suivant. Ainsi, on pourra utiliser PKG_PATH=/usr/ports/packages/amd64/all:scp://user@fqdn/usr/ports/packages/amd64/all:http://ftp.fr.openbsd.org/pub/OpenBSD/snapshots/packages/amd64 mais on devra s'assurer avant tout que les paquets présents dans les deux premiers dépôts sont installables (pas de problèmes de dépendances, de librairies...). Bref, il faut savoir ce qu'on fait.

Maintenant qu'on est bien chaud, on va pouvoir partir à petites foulées dans notre arborescence de fichiers...

3. Petites foulées avec pkglocatedb

Vu que le ports-tree contient la liste de tous les fichiers installables sur le système, on peut ainsi aisément en construire une base de données pour locate(1). Ça se fait via la commande pkg_mklocatedb(1), qui permet aussi de créer une base de données à partir des fichiers du basesystem ou de X, ou sur un sous-ensemble de ports :

$ echo 'games/sl\nmisc/viz' > /tmp/subdirlist
$ SUBDIRLIST=/tmp/subdirlist pkg_mklocatedb -p /usr/ports
sl-3.03p0:
sl-3.03p0:/usr/local/bin/sl
sl-3.03p0:/usr/local/man/man1/sl.1
...

Mais vous n'avez même pas besoin de créer vous-même cette base, car il existe un paquet (databases/pkglocatedb) fournissant une base de données correspondant aux packages installables dans le dépôt, ainsi que la commande pkg_locate (en fait, un bête wrapper sur /usr/bin/locate -d /usr/local/share/pkglocatedb "$@") permettant d'interroger la base.

$ sudo pkg_add pkglocatedb
Ambiguous: choose package for pkglocatedb
 0: <None>
 1: pkglocatedb-0.6p1
 2: pkglocatedb-0.6p1-src
Your choice: 1
pkglocatedb-0.6p1: ok

$ pkg_locate inviz
viz-1.1.1p0:misc/viz:/usr/local/bin/inviz
viz-1.1.1p0:misc/viz:/usr/local/man/man1/inviz.1

À noter qu'il existe une version de cette base de données incluant les fichiers présents dans le basesystem, ainsi que X, permettant de savoir dans quel set d'installation se trouve tel fichier :

$ sudo pkg_add pkglocatedb--src
pkglocatedb-0.6p1-src: ok

$ pkg_locate man4/bge
man55:/usr/share/man/man4/bge.4

$ pkg_locate games/tetris
etc55:/var/games/tetris.scores
game55:/usr/games/tetris

Un peu à la apt-cache search, pkg_locate nous permet donc de répondre à des questions telles que « Mais dans quel §"!$#§"'!§"! de paquet est planqué ce §"%!§% de binaire ?!? », qu'on se pose finalement relativement souvent. Ainsi, quels sont les paquets/sets fournissant un binaire nginx ?

$ pkg_locate bin/nginx
base55:/usr/sbin/nginx
nginx-1.4.7p0-lua:www/nginx/stable,lua:/usr/local/sbin/nginx
...

À noter qu'en 5.6, les bases de données pour les fichiers du basesystem et de X seront intégrées directement au système, et pkglocatedb ne fournira plus que la base de données pour les paquets tiers. On peut bien sûr les interroger directement en utilisant locate -d :

$ locate -d /usr/X11R6/lib/locate/xorg.db xorg.conf
xserv55:/usr/X11R6/man/man5/xorg.conf.5
...
$ locate -d /usr/lib/locate/src.db bge
base55:usr/libdata/perl5/site_perl/amd64-openbsd/dev/pci/if_bgereg.ph
...

4. Nettoyage de printemps avec pkg_check

Comme toute base de données (je parle de /var/db/pkg), il faut un outil pour vérifier son intégrité, et c'est là que pkg_check(1) entre en jeu. En effet, à l'aide de pkg_info, pkg_locate, src.db et xorg.db, il connaît l'ensemble des fichiers supposés être sur le système. Et il peut donc contrôler que :

- tous les fichiers sont bien là, avec les bons attributs ;

- les dépendances entre les paquets sont bien correctes ;

- il n'y a pas de fichiers « supplémentaires » non connus (vieilles librairies, caches d'applications, etc.) qui seraient potentiellement inutiles.

Sur un paquet ayant vu certains de ses fichiers disparaître suite à un nettoyage un peu trop efficace :

$ sudo pkg_check -xqf
--- femail-chroot-0.98p2 -------------------
/var/www/usr/sbin should exist
/var/www/usr/sbin is not a directory

On peut donc utiliser pkg_check pour réparer une installation (dans le cas d'un plantage au milieu d'un pkg_add -u, non non ça n'arrive jamais), mais aussi pour faire du nettoyage. Dans le cycle de vie d'une installation OpenBSD, un certain nombre de fichiers sont supprimés du basesystem, mais jusqu'ici rien ne permettait facilement à l'utilisateur de savoir qu'ils étaient obsolètes et pouvaient donc être « garbage-collectés ». Il lui reste cependant à faire le tri dans la liste...

Exemple, sur des fichiers dans /usr/local ayant été installés/créés par un paquet mais non supprimés correctement, ou non enregistrés comme fichiers temporaires, ou non complètement installés (par exemple Quirks.pm.Dp0xVypQiV) :

Not found:
 /usr/local/lib/gcc-lib/amd64-unknown-openbsd4.7
 ...
 /usr/local/libdata/perl5/site_perl/OpenBSD/Quirks.pm.Dp0xVypQiV
 /usr/local/share/applications/defaults.list
 /usr/local/share/applications/mimeinfo.cache

Sur un système ayant un lourd passif, on trouve plein de trucs à nettoyer :

/usr/include/g++/powerpc-unknown-openbsd4.9
...
/usr/include/g++/powerpc-unknown-openbsd5.4
...
/usr/lib/libc.so.58.3
...
/usr/lib/libc.so.76.0
...
/usr/sbin/apachectl
/usr/sbin/apxs
/usr/sbin/httpd

Tous ces fichiers peuvent raisonnablement être supprimés, plus rien ne devrait en dépendre. Pour les 3 derniers, il s'agissait d'Apache, mais celui-ci a été supprimé en faveur de Nginx, et a été déplacé dans les ports. D'ailleurs, pkg_check nous le dit :

In apache-httpd-openbsd-1.3.20140502:www/apache-httpd-openbsd:
 /etc/rc.d/httpd
 /var/www/cgi-bin/printenv
 /var/www/cgi-bin/test-cgi
 /var/www/conf/httpd.conf
 /var/www/conf/magic
 /var/www/conf/mime.types

Il voit que les fichiers sont encore ici, et nous prévient que ceux-ci existent maintenant comme fichiers du paquet apache-httpd-openbsd.

Enfin, sur un système où tout est correct, on obtient :

$ pkg_check
Packing-list sanity: ok
Direct dependencies: ok
Reverse dependencies: ok
Files from packages: ok
Locating unknown files: ok
Locating unknown directories: ok

pkg_check permet donc de s'assurer de l'intégrité « globale » des fichiers installés sur le système, autant des paquets que de la base.

Maintenant que nous avons vu comment nous y retrouver dans les paquets, on va explorer un peu le ports-tree, mais juste de loin. Sauf que là, on va y aller à grands coups de SQL qui fait mal.

5. Exploration avec sqlports

Le paquet sqlports fournit une base de données aussi, mais cette fois sous SQLite. Ça tombe bien, sqlite3(1) est dans le système de base OpenBSD, on peut donc directement attaquer cette base, à la ligne de commandes (ou via databases/sqlitebrowser), avec notre syntaxe SQL bien aimée.

$ sqlitebrowser /usr/local/share/sqlports

La base présente un schéma relativement complexe, structurant la grande majorité des informations de chacun des ports, et éclatée sur une vingtaine de tables (attention, la version sqlports-compact est encore plus complexe, mais elle n'est pas destinée à être utilisée par un humain, plutôt par une API ayant besoin d'un accès avancé aux données). Les principales tables sont :

- ports : la table principale, une entrée par pkgpath, avec le maximum d'informations pour chacun des ports. Exemple : combien de ports ont leur homepage sur GitHub, et combien de ports n'ont pas de mainteneur déclaré ?

sqlite> select count(*) from ports where homepage like '%github.com%';
284
sqlite> select count(*) from ports where maintainer like '%ports@openbsd.org%';
4155

- depends : liste les dépendances entre pkgpaths, avec un champ TYPE pour savoir si c'est une dépendance de librairie, de compilation, ou d'exécution. Exemple : combien de pkgpaths dépendent de devel/gmake à la compilation ?

sqlite> select count(*) from depends where type='B' and
dependspath='devel/gmake';
1787

- paths : fait la correspondance entre un pkgpath « complet » d'un subpackage tel que net/avahi,no_mono, no_qt3, no_qt4, -gtk3 (3 FLAVORS, SUBPACKAGE -gtk3), et son pkgpath de base (ici net/avahi). Cette table permet aussi de retrouver tous les pkgpaths possibles à partir des combinaisons possibles de FLAVORS et MULTI_PACKAGES :

sqlite> select fullpkgpath from paths where pkgpath='mail/dspam';
mail/dspam,-main
...
mail/dspam,ldap,-pgsql

- wantlib : liste les librairies dont dépend chacun des chemins. Ici, la granularité est au niveau de la librairie directement, pas de la dépendance entre ports ; ainsi, on retrouve toutes les librairies du système de base, de X, et celles installées par les ports (enregistrées, elles, dans la table shared_libs). Exemple : de combien de librairies dépend mail/evolution, et combien de pkgpaths dépendent de sndio, la librairie pour faire du son sous OpenBSD ?

sqlite> select count(*) from wantlib where fullpkgpath='mail/evolution';
46
sqlite> select count(fullpkgpath) from wantlib where value='sndio';
166

- modules : liste les modules (fragments de Makefile BSD utilisés pour factoriser des variables/cibles/dépendances). Exemple : quels sont les chemins utilisant le module www/mozilla ?

sqlite> select fullpkgpath from modules where value like 'www/mozilla';
devel/xulrunner/24,-devel
...
www/mozilla-firefox
www/seamonkey,-main
...

- broken : liste les marqueurs BROKEN, avec l'architecture et la raison. Exemple : quels sont les pkgpaths marqués comme ne compilant pas sur mips64 ?

sqlite> select fullpkgpath,value from broken where arch='mips64el';
audio/flite|GCC hangs compiling cmu_us_kal_diphone.c
...
x11/ogre,-main|missing atomic ops

Maintenant, on peut combiner toutes ces tables... et faire des requêtes plus complexes, telles que « quels sont les noms de paquets ayant une dépendance explicite sur le pkgpath x11/xfce/libxfce4ui » ?

sqlite> select fullpkgname from ports as p \
 left join depends as d where d.fullpkgpath=p.fullpkgpath \
 and d.dependspath='x11/xfce4/libxfce4ui' and d.type='L';
exo-0.10.2p2
...
xfwm4-4.10.1p2

Et, légère différence, « quels sont les paquets qui ont une dépendance directe ou indirecte avec le même pkgpath, et ont donc une de leurs shared_libs dans wantlib » ?

sqlite> select distinct(fullpkgname) from ports as p \
 left join wantlib as w on p.fullpkgpath=w.fullpkgpath \
 left join shared_libs as s on s.libname=w.value \
 where s.fullpkgpath='x11/xfce4/libxfce4ui';
exo-0.10.2p2
...
xfwm4-4.10.1p2

Vous avez une requête bien tordue à faire dans le ports-tree ? Un peu de SQL, et sqlports a la réponse.

On a bien le cerveau en bouillie avec tout ce SQL ; passons à un peu de repos, calme et méditation zen...

6. Informations avec ports-readmes et ports-readmes-dancer

Pour ceux qui ne veulent pas installer l'arbre des ports, et n'ont pas de connexion Internet, il existe une (deux !) alternative(s) : les paquets ports-readmes et ports-readmes-dancer.

Le premier est très simple, n'a aucune dépendance, et fournit une arborescence de pages HTML statiques (une par paquet) triées par catégories.

$ sudo pkg_add ports-readmes
$ lynx /usr/local/share/ports-readme/index.html

Ou si vous préférez Firefox :

$ firefox file:///usr/local/share/ports-readme/index.html

Chacune des pages présente la description du paquet, sa HOMEPAGE, ses catégories, ainsi que la liste de ses dépendances, permettant ainsi de naviguer dans l'arbre des ports.

Le second (ports-readmes-dancer) se présente lui sous la forme d'une application, installe donc un peu plus de dépendances (dont databases/sqlports et www/p5-Dancer) et propose une interface de recherche sur les chemins, les catégories, les mainteneurs...

$ sudo pkg_add ports-readmes-dancer
$ ports-readmes &
>> Dancer 1.3111 server 10773 listening on http://127.0.0.1:3000
== Entering the user dance floor ...
$ lynx http://localhost:3000

Ou encore :

$ firefox http://localhost:3000/search?category=audio&maintainer=landry

Ces deux paquets permettent facilement de s'y retrouver dans l'ensemble des ports disponibles sous OpenBSD.

Maintenant que l'on s'est bien repu d'informations, on va pouvoir attaquer la dernière côte !

7. Sprint final en Perl !

Comme je l'ai mentionné précédemment, les pkg_tools sont écrits intégralement en Perl, et proposent une API (un peu complexe, certes) qui permet à tout un chacun d'écrire un bout de code pour manipuler les paquets installés, faire des statistiques, des recherches sur les miroirs... ; tout ceci est documenté à partir de la manpage OpenBSD::Intro(3p), ainsi que OpenBSD::PackingList(3p) et OpenBSD::PackingElement(3p). Une bonne connaissance des expressions régulières est recommandée...

Avec 4 petits exemples, je vais vous conter comment extraire des informations pertinentes depuis les paquets installés sur votre machine, en utilisant un pattern visiteur. Bim, rien que ça ! Évidemment, on pourrait faire tout ça avec des grep, du shell, des for dans /var/db/pkg, mais il est bien plus élégant d'utiliser l'API native des outils, non ?

7.1 Retrouver la source d'installation de ses paquets

La packing-list de chaque paquet se souvient de la source d'installation via l'annotation @url. En bouclant sur la liste des paquets installés (via installed_packages()), on récupère la packing-list simplifiée avec OpenBSD::PackingList->from_installation() (on n'a pas besoin de l'objet complet, juste d'un sous-ensemble, c'est beaucoup plus rapide) et on peut accéder directement à l'annotation @url avec $plist->{url}->name et la stocker dans un hash ayant comme clé la source, et comme valeur la liste des paquets provenant de cette source.

#!/usr/bin/perl
use warnings;
use strict;
package main;
use OpenBSD::PackingList;
use OpenBSD::PackageInfo; #for installed_packages
use Data::Dumper;
use v5.10;
my %info;
for my $pkg (installed_packages()) {
 # need only UpdateInfo from plist
 my $plist = OpenBSD::PackingList->from_installation($pkg,
 \&OpenBSD::PackingList::UpdateInfoOnly);
 my $url = $plist->{url}->name;
 # strip package file from url
 $url =~ s#/[^/]*$#/#g;
 push @{$info{$url}}, $pkg;
}
say @{$info{$_}}." packages come from $_" foreach keys %info;

La dernière ligne permet d'afficher la taille de chacune des listes (@{$info{$_}}), pour chacune des sources, ce qui produit des statistiques intéressantes sur la provenance des paquets. Évidemment, sur mes machines de développement, j'ai des paquets provenant d'un peu partout :

#dawn:~/$ perl pkg-source.pl
#453 packages come from http://openbsd.cs.toronto.edu/pub/OpenBSD/snapshots/packages/amd64/
#379 packages come from http://ftp.fr.openbsd.org/pub/OpenBSD/snapshots/packages/amd64/
#38 packages come from file:/usr/ports/packages/amd64/all/

#spud:~/$ perl pkg-source.pl
#5 packages come from scp://landry@dusk.rhaalovely.net/usr/ports/packages/amd64/all/
#1 packages come from file:/usr/ports/packages/amd64/all/
#5 packages come from http://rhaalovely.net/stuff/amd64/
...

Tout va bien ? Le Perl ne vous a pas trop fait mal aux yeux ?

7.2 Quel est le paquet le plus vieux sur mon système, et ai-je des paquets non signés ?

Comme nous l'avons vu, la signature d'un paquet est conservée dans l'annotation @digital-signature. L'API d'*OpenBSD::PackingList* permet de savoir si un paquet est signé, et de récupérer la signature, la date de signature et l'identité du signataire... On peut donc récupérer l'ensemble de ces informations, faire ressortir quels paquets sont signés ou pas, et afficher « proprement » la date de signature pour ceux qui le sont. Une fonctionnalité ajoutée récemment au paquet quirks permet d'ailleurs de connaître une liste de paquets ayant des vulnérabilités, et en utilisant la date de signature d'alerter l'utilisateur si une mise à jour n'a pas été trouvée pour le paquet, ce qui montrerait que le miroir qu'il utilise est potentiellement incomplet.

#!/usr/bin/perl
use strict;
use warnings;

package main;
use OpenBSD::PackingList;
use OpenBSD::PackageInfo; #for installed_packages
use OpenBSD::PackingElement; #for time_to_iso8601
use v5.10;
my %info;

for my $pkg (installed_packages()) {
 # need full plist, not ExtraInfoOnly
 my $plist = OpenBSD::PackingList->from_installation($pkg);
 if ($plist->is_signed) {
 my $sig = $plist->get('digital-signature');
 if ($sig->{key} eq 'signify') {
 $info{$pkg}{signedby} = $plist->get('signer')->name;
 $info{$pkg}{timestamp} = $sig->{timestamp};
 $info{$pkg}{timestr} = OpenBSD::PackingElement::DigitalSignature::time_to_iso8601($sig->{timestamp});
 }
 } else {
 $info{$pkg}{unsigned} = 1;
 }
}

On affiche les informations pertinentes en les extrayant du hash %info, ayant comme clef le nom du paquet, et comme valeur un hash contenant le statut du paquet par rapport à sa signature. Une petite astuce foreach sort { $info{$a}{timestamp} <=> $info{$b}{timestamp} } permet de trier les informations par date de signature pour un affichage plus pertinent.

my @unsigned = grep { defined $info{$_}{unsigned} } keys %info;
my @signed = grep { defined $info{$_}{signedby} } keys %info;
say 'unsigned packages: '.@unsigned;
say $_ foreach sort @unsigned;

say 'signed packages: '.@signed;
say "$info{$_}{timestr}:$_"
 foreach sort { $info{$a}{timestamp} <=> $info{$b}{timestamp} } @signed;

Ici, comme tout à l'heure, sur mes machines de développement, j'ai souvent des paquets non signés car je les produis moi-même, ou non complètement à jour, mais une machine « normale » devrait uniquement avoir des paquets signés et à jour. Les paquets de firmware sont un peu particuliers, ils n'ont pas de raison d'être mis à jour tant qu'il n'y a pas de nouvelle version disponible :

dawn:~/$ perl signature-date.pl
unsigned packages: 216
...
wdg-sgml-lib-1.1.4p0
...
2014-01-18T01:36:22Z:gcc-4.6.4p7
...
2014-07-20T00:05:25Z:quirks-2.1

spud:~/$ perl signature-date.pl
unsigned packages: 33
firefox-31.0beta8
...
wireshark-1.10.8
signed packages: 318
2014-03-03T08:14:44Z:iwn-firmware-5.10p0
...
2014-07-08T12:50:10Z:sqlports-4.0

Ça va toujours ? Maintenant, on passe au pattern visiteur !

7.3 Qui est le plus gourmand ?

Ici, on veut retrouver pour chacun des paquets quel est le fichier ayant la taille la plus grosse, pour faire faire une cure d'amaigrissement à notre système... On va utiliser la construction visit() de l'objet OpenBSD::PackingList, en lui passant un nom de méthode qui sera appelé pour chacun des objets de la packing-list. Bam, le visiteur :

#!/usr/bin/perl
use warnings;
use strict;

my %info;

package OpenBSD::PackingElement;
sub method
{
}

Petit détail d'implémentation, il faut définir une méthode vide pour la classe de laquelle chacun des objets PackingElement hérite.

package OpenBSD::PackingElement::FileBase;
sub method
{
 my ($self, $pkg) = @_;
 if (defined $self->{size} &&
 $self->{size} > $info{$pkg}{maxsize}) {
 $info{$pkg}{maxsize} = $self->{size};
 $info{$pkg}{maxpath} = $self->{name};
 }
}

On compare la taille du fichier actuel (la méthode est implémentée uniquement pour les objets de packing-list de type FileBase, des vrais fichiers donc, pas des annotations) et on la compare avec la taille maximale qu'on avait trouvée jusqu'ici pour ce paquet. Du classique.

package main;
use OpenBSD::PackingList;
use OpenBSD::PackageInfo; #for installed_packages
use Data::Dumper;
use v5.10;

for my $pkg (installed_packages()) {
 # we need the full plist to visit file sizes
 my $plist = OpenBSD::PackingList->from_installation($pkg);
 $info{$pkg}{maxsize} = 0;
 $plist->visit('method', $pkg);
}

On visite chacun des paquets...

say "for $_, max size is $info{$_}{maxsize} with $info{$_}{maxpath}"
 foreach sort { $info{$a}{maxsize} <=> $info{$b}{maxsize} }
 grep { $info{$_}{maxsize} != 0 } keys %info;

Et on affiche les résultats pour l'ensemble des paquets ayant des fichiers (oui, il y a des meta-paquets n'installant aucun fichier) en triant les résultats par ordre croissant de taille. Ce qui donne :

spud:~/ $perl visit-size.pl
for xfce-4.10p1, max size is 3287 with share/doc/pkg-readmes/xfce-4.10p1
for p5-HTTP-Server-Simple-PSGI-0.14, max size is 5530 with libdata/perl5/site_perl/HTTP/Server/Simple/PSGI.pm
....
for firefox-31.0beta8, max size is 77310704 with lib/firefox-31.0/libxul.so.48.0

dawn:~/ $perl visit-size.pl
for p5-Class-Accessor-Chained-0.01p0, max size is 1666 with libdata/perl5/site_perl/Class/Accessor/Chained.pm
...
for spidermonkey-24.2.0, max size is 360389550 with lib/libmozjs-24.a

On retrouve bien les habituels monstres aux premières places : les Mozilla, les webkits, les llvm, les librairies statiques. À noter que ça a permis de corriger un bug dans le paquet spidermonkey, qui ne strippait pas ses librairies statiques qui faisaient donc plus de 300 Mi sans raison.

7.4 Et les dépendances, ça se passe comment ?

Les dépendances sont exprimées de deux manières :

- avec la notion de WANTLIB, un des binaires du paquet est relié à la librairie correspondante : il y a une dépendance bas niveau sur cette librairie, qui peut venir du système de base ou d'un autre paquet tiers. Par exemple, la plupart des paquets ont une WANTLIB sur la libc du système.

- la notion de DEPEND où le paquet déclare qu'il a besoin d'un autre paquet pour compiler et fonctionner correctement. C'est une dépendance de plus haut niveau. Par exemple, Firefox va dépendre (entre autres) de x11/gtk+2 et security/nss.

En parcourant les dépendances enregistrées pour chacun des paquets installés, on peut ainsi voir quelles sont les librairies les plus utilisées, ainsi que les paquets étant les plus gourmands en dépendances. « J'ai installé vlc et j'ai la moitié du miroir qui est venue avec... »

#!/usr/bin/perl
use warnings;
use strict;

my %depend;
my %wantlib;
my %rdepend;
my %rwantlib;

package OpenBSD::PackingElement;
sub method
{
}

package OpenBSD::PackingElement::Wantlib;
sub method
{
 my ($self, $pkg) = @_;
 push @{$wantlib{$self->{name}}}, $pkg;
 push @{$rwantlib{$pkg}}, $self->{name};
}

package OpenBSD::PackingElement::Dependency;
sub method
{
 my ($self, $pkg) = @_;
 push @{$depend{$self->{name}}}, $pkg;
 push @{$rdepend{$pkg}}, $self->{name};
}

On va stocker les deux types de dépendances dans quatre hashes, pour pouvoir aussi traiter les dépendances inverses. Ainsi, un des hashes aura comme clé la dépendance et comme valeur la liste des dépendants, et l'autre aura comme clef le dépendant et comme valeur la liste des dépendances.

package main;
use OpenBSD::PackingList;
use OpenBSD::PackageInfo; #for installed_packages
use Data::Dumper;
use v5.10;

for my $pkg (installed_packages()) {
 my $plist = OpenBSD::PackingList->from_installation($pkg,
 \&OpenBSD::PackingList::DependOnly);
 $plist->visit('method', $pkg);
}

Rien que du classique, mais on demande juste l'information de dépendance à from_installation() en lui disant qu'on veut un objet de type OpenBSD::PackingList::DependOnly, c'est plus rapide.

say @{$wantlib{$_}}." packages depend on WANTLIB $_"
 foreach (sort { @{$wantlib{$b}} <=> @{$wantlib{$a}} } keys %wantlib)[0..3];
say "$_ depends on ".@{$rwantlib{$_}}." WANTLIBs"
 foreach (sort { @{$rwantlib{$b}} <=> @{$rwantlib{$a}} } keys %rwantlib)[0..3];
say @{$depend{$_}}." packages depend on pkgname $_"
 foreach (sort { @{$depend{$b}} <=> @{$depend{$a}} } keys %depend)[0..3];
say "$_ depends on ".@{$rdepend{$_}}." pkgnames"
 foreach (sort { @{$rdepend{$b}} <=> @{$rdepend{$a}} } keys %rdepend)[0..3];

Enfin, quatre boucles similaires pour afficher les résultats, avec un peu de voodoo Perl pour n'afficher que les quatre premiers, en triant toujours par la taille des listes. La construction ()[0..3] s'assure qu'on fait d'abord le tri sur la taille des listes (via le sort { @{$wantlib{$b}} <=> @{$wantlib{$a}} }), puis qu'on ne traite que les quatre premières valeurs de cette liste triée. Ce qui nous donne... relativement peu de surprises. Énormément de paquets dépendent de libiconv/gettext/python, et les gros consommateurs sont les habituels vlc/webkit/gstreamer (et je n'ai pas GNOME 3 d'installé !).

#dawn:~/$ perl visit-depend.pl
#341 packages depend on WANTLIB c.75.0
#279 packages depend on WANTLIB m.9.0
#215 packages depend on WANTLIB pthread.18.0
#208 packages depend on WANTLIB iconv.6.0
#gstreamer-plugins-good-0.10.31p11v0 depends on 67 WANTLIBs
#gvfs-1.20.2 depends on 62 WANTLIBs
...
#gdal-1.11.0 depends on 20 pkgnames
#p5-Moose-2.1204 depends on 16 pkgnames

#renton:~/$ perl visit-depend.pl
#501 packages depend on WANTLIB m.9.0
#490 packages depend on WANTLIB c.77.0
#403 packages depend on WANTLIB iconv.6.0
#395 packages depend on WANTLIB pthread.18.0
#vlc-2.0.10p1 depends on 100 WANTLIBs
...
#xine-lib-1.2.6 depends on 24 pkgnames
#gstreamer-plugins-bad-0.10.23p16 depends on 23 pkgnames

J'aurais pu faire un parcours récursif des hashes pour trouver la chaîne de dépendances la plus longue, mais : head explodes divided by zero !

7.5 pkg_mgr

pkg_add c'est pas mal, mais c'est moyennement « GENS-friendly » pour ceux que la ligne de commandes effraie ; connaissant aptitude/synaptic chez Debian, je me suis attelé en 2008 à la création d'une surcouche graphique à pkg_add (en Perl, what else ?) utilisant dans un premier temps l'interface console Curses::UI (Fig. 1) et visant dans un second temps une interface GTK+ (pour l'instant à l'état de prototype, Fig. 2). Il peut être installé directement sur un système OpenBSD via pkg_add pkg_mgr.

Fig. 1 : pkg_mgr avec son interface console

Fig. 2 : pkg_mgr avec l'interface GTK+

Pour les recherches, il était évident d'utiliser databases/sqlports, en utilisant les modules d'accès aux bases de données DBD::SQLite. Il m'a aussi permis d'expérimenter les modules d'ORM (Object Relational Mapping) de Perl tels que DBIx::DB et Rose::DB, mais après quelques tests de performance, ces derniers n'ont pas été retenus, certainement à cause de mes piètres compétences en Perl, car la base de données prenait trop de temps pour être préchargée au démarrage de l'application.

pkg_mgr a dans un premier temps juste été une surcouche appelant pkg_add pour les installations/suppressions de paquets, mais j'ai rapidement intégré directement l'API Perl des pkg_tools dans l'application, pour avoir une bien meilleure interactivité. Ainsi, les questions posées par les pkg_tools à l'utilisateur (choix entre plusieurs alternatives pour un paquet, confirmation de suppression...) sont correctement interceptées, et la question s'affiche directement avec les composants graphiques de l'interface. Idem pour les barres de progression lors de l'installation/suppression de paquets.

Le modèle MVC est aussi implémenté, pour avoir une séparation claire entre la base de données/liste des paquets installés, l'interface graphique utilisée, et toute la logique métier pour faire communiquer ces deux briques.

Ce projet est actuellement en sommeil depuis 2011 faute de temps (et comporte donc certainement quelques bugs, car l'API des pkg_tools a légèrement évolué depuis), mais toutes contributions sont bien évidemment les bienvenues (voir http://dawn.rhaalovely.net/pkg_mgr/).

Conclusion

J'espère dans cet article vous avoir fait découvrir une partie des nombreux outils disponibles sous OpenBSD pour travailler avec les paquets tiers ainsi que leur structure interne, et que cela vous aura donné envie de mettre votre grain de sel dans ce projet, toujours à la recherche de nouveaux contributeurs ! Promis, on ne mord pas !

Tags : OpenBSD, paquets, Perl