Skip to content

Linux Attitude

Le libre est un état d'esprit

Archive

Archive for December, 2007

Niveau :      
Résumé : brainfuck ; beef

Aujourd'hui détente avec un langage de programmation que vous n'utiliserez que très rarement. Il s'agit de brainfuck.

Brainfuck est un langage Turing-complet, ce qui veut dire qu'il est possible d'écrire en brainfuck n'importe quelle fonction qu'il serait possible d'écrire avec un autre langage.

Brainfuck ne dispose que de 8 fonctions, toutes s'écrivent sur un seul caractère. ces fonctions manipulent un unique pointeur. Ce pointeur peut se déplacer d'unité en unité sur un tableau infini (ou presque) en mémoire. Sur ces cases mémoire, il peut incrémenter et décrémenter à volonté les valeurs. Tout doit se faire avec ces bases. Ainsi pour ajouter 5 à une case mémoire, vous devez incrémenter 5 fois le contenu de la case, et je ne vous parle pas des multiplications ;-)

Les fonctions de base sont :

  • > : Incrémenter le pointeur
  • < : Décrémenter le pointeur
  • + : Incrémenter l'octet pointé
  • - : Décrémenter l'octet pointé
  • . : Écrire l'octet pointé sur la sortie
  • , : Lire un octet pour le mettre dans la case pointée
  • [ : Saute au ] correspondant si le contenu du pointeur est nul (goto if 0)
  • ] : Saute au [ correspondant si le contenu du pointeur est non nul (goto if !0)

Vous trouverez quelques programmes d'exemple en brainfuck sur le site de brainfuck

Pour développer en brainfuck, il vous faut un interpréteur. Vous le trouverez sous le nom de beef chez debian.

Malgré le peu de lisibilité des programmes obtenus, vous constaterez que c'est le langage dans lequel cat s'écrit de la façon la plus courte possible

,[.,]

Et maintenant, pour vous amuser, essayez de coder l'addition, la multiplication voire la division (sans regarder sur wikipedia s'il vous plait !). Ça vous ouvrira un peu l'esprit et vous changera du sudoku.

Après être devenu un pro, passez au calcul en virgule flottante.

Niveau :      
Résumé : screen -x

Un article pour ceux qui ont des amis geeks. Donc supposons que vous en ayez, sinon un stagiaire ou un chat feront l'affaire. En tant qu'ami intime, il peut partager votre terminal. Pour cela nous allons utiliser screen. Premier exemple avec deux terminaux et le même utilisateur :

# terminal 1
$ screen -S moi
# terminal 2
$ screen -x moi

Mais même un ami n'a pas accès à votre compte. Première solution, passons sur son compte à travers le compte root, attention, il y a un problème de droit au passage.

# terminal 1
$ screen -S moi
# terminal 2
$ su -
$ chmod a+rw `tty`
$ su - monami
$ screen -x moi

Ce qui est bien, mais pas top. Faisons donc les choses dans l'autre sens, c'est la personne dans le terminal 1 qui accordera à celle de terminal 2 le droit de lire son terminal. Pour cela, il faut que screen soit setuid root. C'est un risque, si un utilisateur trouve une faille dans screen, peut-être qu'il pourra devenir root.

$ chmod +s /usr/bin/screen

Attention, si screen est setuid root, la variable SCREENDIR ne fonctionnera plus.

Mettons donc le maître sur le terminal1.

# terminal 1
$ screen -S moi
Ctrl-a :multiuser on
Ctrl-a :addacl user2
# terminal 2
$ screen -x user1/moi

Wahoo, c'est cool ! Maintenant vous voulez que l'utilisateur qui vous espionne ne puisse pas interagir avec votre ligne de commande (si vous voulez jouer au prof avec votre chat). Il suffit simplement de lui retirer les droits qui sont activés par défaut.

Ctrl-a :chacl user2 -wx "#?"

Notez que vous pouvez mettre ces commandes dans votre .screenrc :

multiuser on
addacl user2
# cette commande ne fonctionne pas dans le sceenrc
chacl user2 -wx "#?"

Niveau :      
Résumé : vrms

Vous savez bien évidemment qui est RMS, Richard Stallman, un homme à la source du concept du logiciel libre. Il est a l'origine de GNU, de la GPL, de la FSF et de nombreux outils comme emacs.

Comme vous le savez peut-être un peu moins, selon Richard Stallman (et la GPL) c'est le code qui doit être libre. Il est interdit d'un façon ou d'une autre de de priver le code de sa liberté. Au contraire, dans la philosophie BSD c'est le développeur qui doit rester libre.

Richard Stallman est aussi une sorte d'extrémiste, il est connu pour ses positions bien tranchées sur ce qui est suffisamment libre pour être utilisé et ce qui ne l'est pas. Quelques trolls viennent régulièrement égayer les mailing-list de divers projets grâce à lui.

Debian est un grand admirateur de RMS, bien que ce dernier considère debian comme insuffisamment libre. On peut donc trouver sur debian un paquet nommé vrms. Ce paquet a la particularité de se comporter comme Richard stallman, d'où son nom : Virtual RMS. Si vous l'installez, vous recevrez par mail une fois par mois la liste des paquets installés sur votre machine qui ne sont pas libres.

Question subsidiaire : savez vous ce que signifie le M de RMS ? ?

Niveau :      
Résumé : syscall

Qu'est-ce qu'un appel système ?

Prenons le code suivant :

fopen("/tmp/toto", "r");

Ceci est simplement un appel de fonction, fonction écrite dans la libc et donc disponible à tout bon programme écrit en C (notez que le manuel de fopen est dans la section 3)

Prenons ensuite le code suivant :

open("/tmp/toto", O_RDONLY);

Ceci vous semble aussi être un appel de fonction comme les autres. Hé bien vous avez presque raison. En fait cette fonction est dans la libc, mais celle-ci ne fait que rediriger l'appel vers une fonction du noyau. C'est un appel système (syscall), notez cette fois que le manuel de open est dans la section 2.

Quels sont-ils ?

Il existe un grand nombre d'appels système. Tous sont numérotés, et ce numéro ne peut pas changer, en effet, le changer reviendrait à casser tous les binaires fonctionnant sous linux. Par contre, il arrive de temps en temps qu'un appel système soit ajouté, mais ce genre de chose est toujours étudié en profondeur, car cela signifie qu'il devra être maintenu à vie par les développeurs du noyau. C'est ce qu'on appelle l'API stable du noyau.

Parmi les appels système, on trouve de tout, enfin tout ce qui est du ressort du noyau. Vous pourrez en trouver la liste dans les sources. Toutes les fonctions concernant le système de fichiers, le réseau, les droits, les utilisateurs ou les processus se retrouvent ici.

Comment ça marche ?

L'implémentation d'un appel système dépend complètement du processeur. En effet, il faut une instruction qui offre quelques garanties au noyau pour permettre son bon fonctionnement.

Ces garanties sont des privilèges particuliers accordés au code au code appelé. Par exemple, le code appelé peut avoir des droits d'accès au matériel et il ne peut pas être modifié par le code appelant.

Intéressons-nous à des processeurs assez répandus, les x86. L'instruction qui nous intéresse est l'interruption (int). Linux a choisi l'interruption 80 pour mettre en place ses appels système. Mais tout ceci est caché par la libc qui fait elle-même l'appel. Mais l'instruction int est infiniment lente et Intel a décidé de fournir une autre instruction du même style, en plus rapide (sysenter). Il fallait donc une méthode pour que linux utilise cette nouvelle instruction sans pour autant casser les logiciels existants.

Linus a choisi de permettre les appels système sans avoir à se se poser la question de la méthode à utiliser. Depuis linux 2.5, tous les processus disposent d'une page spéciale créée par le noyau contenant le code pour faire l'appel système. Et c'est lui qui choisi la meilleure méthode disponible. On appelle cette page la VDSO (Virtual Dynamically-linked Shared Object). C'est elle que vous voyez lorsque vous faites un ldd sur un exécutable et qu'il contient l'une de ces lignes :

linux-gate.so.1 =>  (0xffffe000)
linux-vdso.so.1 =>  (0x00007fffa6dfe000)

PS : int 80 reste disponible sur toutes les version de linux

PPS : fopen fait appel à open en interne

PPPS : pour mémoire dos avait choisi l'interruption 21 pour ses propres fonctions

Niveau :      
Résumé : date ; vi

Convertir un timestamp en date :

$ perl -e '$t=localtime($ARGV[0]); print "$t\n";'

D'où l'alias utile :

$ alias sdate='perl -e "\$t=localtime(\$ARGV[0]); print \"\$t\\n\";"'

En vi pour commenter une ligne :

:s/^/#/

Pour commenter plusieurs lignes :

v
# sélectionnez vos lignes
:s/^/#/

Pour commenter toutes les lignes :

:%s/^/#/

Et n'oubliez pas de décommenter les lignes :

:%s/^#//

Pour lancer une commande dans un screen déjà lancé en une commande :

$ screen -S ma_session -X exec ma commande

Attention, la session screen doit avoir au moins été affichée une fois (ie pas lancée avec -d -m)

Niveau :      
Résumé : cmsg_type = SCM_RIGHTS

Ne vous-êtes vous jamais posé la question de comment fait apache pour lancer des processus à l'avance tout en continuant de recevoir les connexions sur le processus père ? Les cours de programmation sur les sockets ne proposent que deux façons de coder un serveur. Vous écoutez (listen) sur un processus et à chaque nouvelle connexion d'un client (accept) vous obtenez une nouvelle socket. Ensuite, soit vous créez (fork) un nouveau processus qui communiquera uniquement avec ce client sur la socket ainsi créée, soit vous gérez toutes les sockets dans le même processus (merci select).

Bien, je vous propose de continuer à faire gérer les connexions par des processus fils, tout en lançant ceux-ci à l'avance. Pour cela, il suffit de faire en sorte qu'un processus puisse communiquer un file descriptor (ou une socket) à un autre. La solution est relativement simple si on est habitué à la gestion des sockets en C. Elle se trouve dans man 7 PF_UNIX à la section "Ancillary Messages".

Les socket unix permettent de communiquer certaines informations non communicables par le réseau. Dans notre cas, nous allons communiquer des file descriptor. Il nous faut donc une socket unix pour communiquer entre les différents processus. Vous trouverez en attaché le source pour deux programme. Le premier (listener) écoute les connexions sur le port tcp 2000, dès qu'une connexion est ouverte, il communique le fd résultant au second (worker) qui va simplement afficher un petit message. Notez que la communication passe par une socket unix (/tmp/transmit) et que listener doit être lancé en premier.

Voici un aperçu des fonctions intéressantes :

Transmission du file descriptor

void fd_transmit(int unixfd, int fd)
{
 [ ... ]
        /* on transmet le minimum de données (1 octet) 
         * pour que le message soit pris en compte */
        iov.iov_base    = &one_char;
        iov.iov_len     = 1;
        msg.msg_iov     = &iov;
        msg.msg_iovlen  = 1;

        /* on spéficie un header spécial précisant qu'on transmet une socket */
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_len   = CONTROLLEN;
        cmsg->cmsg_type  = SCM_RIGHTS;
        *(int *)CMSG_DATA(cmsg) = fd;

        msg.msg_control = cmsg;
        msg.msg_controllen = CONTROLLEN;

        /* ce type de message doit être envoyé avec sendmsg */
        rval = sendmsg(unixfd, &msg, 0);
[ ... ]
}

continue reading...

Niveau :      
Résumé : init ; telinit ; /etc/init.d/rc

Qu'est-ce qu'un runlevel ?

C'est un niveau représentant l'état du système dans lequel tourne votre unix. Ce niveau porte un nom (relativement arbitraire) et définit un ensemble de programmes qui doivent être lancés ou arrêtés.

Qui gère les runlevels ?

C'est init qui s'occupe de définir le runlevel quand on lui demande.

Comment connaître le runlevel courant ?

$ runlevel #root
$ who -r #user

Comment changer un runlevel ?

Le runlevel est défini au boot par un paramètre au noyau, paramètre qui est retransmis à init. Pour changer le runlevel en cours de fonctionnement du système, il faut utiliser (en tant que root) la commande telinit

$ telinit 6

Comment changer le runlevel par défaut ?

Vous pouvez soit passer le paramètre au noyau au boot (linux 2), soit modifier /etc/inittab :

# runlevel 2 par défaut
id:2:initdefault:

Quels sont les runlevels existants ?

Les runlevel portent des numéros, bien que cela soit totalement arbitraire et n'impliquent pas de notion d'ordre. 3 niveaux sont clairement définis:

  • 0 : arrêt de la machine
  • 1 : mode mono utilisateur (en général sans réseau)
  • 6 : reboot de la machine

Les niveaux 2 à 5 sont laissés à la libre imagination des distributions. Pour debian ils sont tous identiques, 2 étant le niveau par défaut. Sous redhat, seuls le 3 (sans interface graphique) et le 5 (avec interface graphique) sont définis, celui par défaut étant le 5.

De plus un autre niveau existe, nommé S, il s'agit d'un niveau lancé lors du lancement de la machine avant les autres, quel que soit le runlevel. On l'appelle souvent single user bien que son nom devrait plutôt être start car cette fois il y a bien une notion d'ordre.

Où sont définis les runlevel ?

La définition se fait à deux endroits. Tout d'abord dans la configuration de init, c'est-à-dire dans /etc/inittab. C'est ici qu'on trouve les commandes qui doivent être lancées à chacun des runlevel. L'autre partie se situe dans /etc/rcX.d (où X représente le niveau) ou /etc/rc.d/rcX.d selon votre distribution.

Comment init change de runlevel ?

Init lit tout simplement son fichier de configuration /etc/inittab. Il repère toutes les lignes qui contiennent le nouveau runlevel dans son 2e champ. Vous y verrez donc en général le lancement des terminaux linux et quelques autres détails. Mais surtout, vous trouverez le lancement de /etc/init.d/rc, ceci est un script qui va lire sa configuration à un nouvel endroit.

Il va lire le contenu du répertoire /etc/rcX.d (où X est le nouveau runlevel) et lancer dans l'ordre tous les scripts qu'il y trouve. Ceux qui commencent par un K avec le paramètre stop et ceux qui commencent par un S avec le paramètre start. Attention, toutefois, il ne prend en compte que les scripts dont le nom commence par K ou S suivi de 2 chiffres.