Rolinh

Rolinh' release

Un Dépôt Pour Ses Fichiers De Configuration Et Un Rakefile Pour Les Déployer

J’ai plusieurs articles plutôt conséquents en préparation (un tutoriel sur les Makefile ainsi qu’une explication très détaillée du fonctionnement de l’algorithme RSA avec implémentation en Ruby) mais comme je ne trouve pas vraiment le temps de les finir en ce moment, j’en profite pour publier un petit article.

Un ami m’a demandé aujourd’hui comment je gérais mes fichiers de configuration; ces fameux dotfiles, ou fichiers cachés, présents dans $HOME ou $XDG_CONFIG_HOME. La réponse est simple: via un dépôt git. Oui mais… comment les copier dans $HOME ensuite? Simplement via un script, ou plus exactement un Rakefile qui s’occupe de faire des liens symboliques vers les fichiers de mon dépôt. J’utilisais auparavant un script shell (assez moche d’ailleurs) mais bon, un script shell, même si ça rend parfois bien service, c’est pas beau.

Voici donc la procédure que j’utilise: Lorsque j’ai accès à une nouvelle machine, mon premier réflexe est d’installer mes outils indispensables, comme vim, zsh et git. Une fois ceci fait, je clone mon dépôt de fichiers de configuration, je me déplace dedans via un terminal, je lance un rake install et hop, je me sens comme chez moi. Simple non? Mais comment ça marche? Pour les non-familiers, rake est un outil similaire à make, qui fonctionne donc avec un Rakefile en lieu et place d’un Makefile. C’est un outil écrit en Ruby que l’on peut facilement installer via une gem.

Quel avantage par rapport à make me direz-vous? Et bien simplement le fait que l’on peut profiter de toute la puissance du langage haut-niveau qu’est Ruby pour définir des tâches qui sont les cibles de notre Rakefile et que l’on peut appeler des méthodes (comprenez fonction) définies en Ruby directement depuis ces tâches.

Dans le cas présent, seule une tâche est définie: install. Cette tâche s’occupe donc, comme je l’ai dit, de lier tous mes fichiers de configuration mais aussi de changer mon shell pour zsh, si ce dernier est installé sur le système, via des méthodes définies également dans le Rakefile. Bon assez parlé, le voici donc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
require 'rake'

desc "links configuration files from this repository to $HOME"
task :install do
  dest = ENV['HOME']
  puts "Linking to ~"
  link_folder_content(dest)

  dest = dest + "/.config"
  puts "Linking to ~/.config"
  Dir.chdir("config")
  link_folder_content(dest)

  check_shell("/bin/zsh")
end

def link_folder_content(dest)
  replace_all = false

  Dir['*'].each do |file|
      next if %w[config Rakefile README linkconfig.sh].include? file

      file = "." + file if dest = ENV['HOME']

      if File.exist?(File.join(dest, "#{file}"))
          if replace_all
              replace_file(dest, file)
          else
              print "overwrite #{dest}/#{file}? [ynaq] "
              case $stdin.gets.chomp
              when 'a'
                  replace_all = true
                  replace_file(dest, file)
              when 'y'
                  replace_file(dest, file)
              when 'q'
                  exit
              else
                  puts "skipping #{dest}/#{file}"
              end
          end
      else
          link_file(dest, file)
      end
  end
end

def replace_file(dest, file)
  system %Q{rm -v "#{dest}/#{file}"}
  link_file(dest, file)
end

def link_file(dest, file)
  system %Q{ln -sv "$PWD/#{file.sub(/^(.?)/, "")}" "#{dest}/#{file}"}
end

def check_shell(shell)
  if ENV['SHELL'] != shell
      if command?(shell)
          system "chsh -s #{shell}"
      else
          puts "#{shell} not installed. Skipping."
      end
  else
      puts "#{shell} is already set as default shell."
  end
end

def command?(cmd)
  system %Q{which "#{cmd}" > /dev/null 2>&1}
end

Le script est certainement très facilement adaptable à votre besoin. Il est à noter que, dans mon dépôt, mes fichiers et dossiers ne sont pas précédés d’un “.”. La ligne 21 permet d’éviter de créer des liens vers des fichiers où dossiers pour lesquels ce n’est pas nécessaire. À vous donc de l’adapter au même titre que la tâche install.

Ah, et si mes fichiers de configuration vous intéressent, vous pouvez toujours accéder mon dépôt git ici

Monitorer Son Espace Disque Avec Dfc

Lorsque l’on doit gérer des serveurs, une des choses à prendre en compte est l’évolution du besoin en terme d’espace de stockage. En effet, ceux-ci évoluent au cours du temps et, afin de ne pas être pris de court, un bon administrateur systèmes se doit de mettre en place une solution lui permettant d’être informé de cette évolution. Cela lui permet de pouvoir planifier des mesures en conséquence, telles que l’achat de nouveaux disques durs ou d’un SAN.

Il existe de nombreuses solutions pour effectuer cette tâche mais si vous ne souhaitez pas prendre un bazooka pour tuer une mouche, alors dfc combiné à une tâche cron peut tout à fait faire l’affaire. En effet, depuis sa version 3.0.0, dfc permet l’export vers CSV et HTML, ce qui se révèle bien pratique pour cette tâche.

Dans cet article, je vais vous présenter les bases d’un script capable de relever le taux d’utilisation des disques d’une machine fonctionnant sous Linux, BSD ou OSX et d’envoyer le rapport par e-mail avec les versions en HTML et CSV en pièce jointe.

Il est d’abord nécessaire de définir quelques variables:

1
2
3
4
MAILADDRESS=john.doe@domain.tld
TMPFSREPORTHTML=/tmp/dfc-report.html
TMPFSREPORTCSV=/tmp/dfc-report.csv
BOUNDARY="========== BOUNDARY =========="

Il est également nécessaire de définir la fonction qui s’occupera effectivement de générer les données pour le rapport. À noter que si dfc n’est pas disponible sur le serveur pour une raison ou un autre, la commande df est appelée en remplacement:

1
2
3
4
5
6
7
8
9
10
fsusage() {
    echo "FILESYSTEM SPACE USAGE REPORT"
    if command -v dfc &>/dev/null; then
        echo "$(dfc -t -tmpfs,rootfs,devtmpfs -c never -disTW)"
        dfc -t -tmpfs,rootfs,devtmpfs -c always -disTWo -e html > $TMPFSREPORTHTML
        dfc -t -tmpfs,rootfs,devtmpfs -c never -disTWo -e csv > $TMPFSREPORTCSV
    else
        echo "$(df -Th)"
    fi
}

Étant donné que l’on souhaite envoyer le tout par email, une fonction qui génère son entête s’avère bien utile:

1
2
3
4
5
6
7
8
9
10
11
12
wheader() {
    echo "From: $(hostname -s)@$(hostname --domain)"
    echo "To: $MAILADDRESS"
    echo "Subject: Disk usage report for $(hostname)"
    if command -v dfc &>/dev/null; then
        echo "Mime-Version: 1.0"
        echo -e "Content-Type: Multipart/Mixed; boundary=\"$BOUNDARY\"\n"
        echo "--${BOUNDARY}"
    fi
    echo -e "Content-Type: text/plain\n"
    echo -e "Disk usage report for $(hostname) on $(date)\n"
}

Et puis une autre qui attache les pièces jointes (les fichiers CSV et HTML):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mailattach() {
    if [ -f $TMPFSREPORTHTML ]; then
        echo "--${BOUNDARY}"
        echo "Content-Type: text/html; name="${TMPFSREPORTHTML}
        echo "Content-Disposition: attachment; filename="`basename ${TMPFSREPORTHTML}`
        echo
        cat $TMPFSREPORTHTML
        echo
    fi
    if [ -f $TMPFSREPORTCSV ]; then
        echo "--${BOUNDARY}"
        echo "Content-Type: text/csv; name="${TMPFSREPORTCSV}
        echo "Content-Disposition: attachment; filename="`basename ${TMPFSREPORTCSV}`
        echo
        cat $TMPFSREPORTCSV
        echo
    fi
    echo -e "--${BOUNDARY}--\n"
}

On a désormais tout ce qu’il faut. Il suffit donc simplement d’appeler ces fonctions et d’envoyer le tout par email. Bien évidemment, je considère ici que sendmail ou autre serveur d’envoi est installé et fonctionnel:

1
2
3
4
5
{
    wheader
    fsusage
    mailattach
} | sendmail -i -t

Pour bien faire, il faudrait générer un fichier de log mais je n’estime pas que des explications soient nécessaires pour cela.

EDIT: dfc se trouve sur ici s’il n’est pas déjà disponible en version >= 3.0.0 dans les dépôts de votre distribution.

Un Script Pour Mettre à Jour Ses Dépôt Git

Si vous êtes un peu comme moi, vous jonglez avec des dizaines de dépôts git. Ceux auxquels je participe se limitent à une vingtaine environ mais j’ai des dizaines de clones que je consulte à titre d’intérêt, comme ceux du noyau Linux, NetBSD ou autre Awesome WM. Bref, à ce jour, j’ai environ 21G de code source provenant de différents dépôts git sur mon laptop…

Évidemment, j’aime bien les tenir à jour. Donc, afin de me faciliter la tâche, j’ai écrit un simple script shell que j’ai placé à la racine du dossier contenant tous les clones de dépôt git. Il se charge de trouver tous les dossiers de dépôt git et d’y effectuer un git pull.

Le script est tout simple mais bon, comme ça peut éventuellement en arranger d’autres, je partage. ;–)

1
2
3
4
5
6
#!/bin/sh

for d in $(find . -type d -name ".git" | sed 's/\/.git\+$//' ); do
    echo "Current repository: $d";
    cd $d && git pull && cd - || exit 1
done

Si vous savez que vos clones ne se trouvent pas trop loin dans votre arborescence, vous pouvez limiter la recherche en profondeur du find via l’option maxdepth. Par exemple, pour limiter la recherche de dépôts git à une profondeur de 2, il faut modifier le script ainsi:

1
2
3
4
5
6
#!/bin/sh

for d in $(find . -maxdepth 2 -type d -name ".git" | sed 's/\/.git\+$//' ); do
    echo "Current repository: $d";
    cd $d && git pull && cd - || exit 1
done

Bien évidemment, ce script s’arrête s’il rencontre une erreur lors d’un pull sur un dépôt donc vous pouvez l’utiliser sans craintes.

Il va sans dire qu’une légère modification permet de l’utiliser pour d’autres types de dépôts. Par exemple, il suffit de remplacer “.git” par “.hg” et le git pull par un hg pull -u pour qu’il fonctionne avec les dépôts mercurial.

EDIT: Un lecteur m’a très justement fait remarquer qu’il existait la variable d’environnement $OLDPWD, accessible par l’alias -. J’ai donc simplifié le script en conséquence. Merci à lui.

Les Pointeurs Sur Fonction en C

Cela fait longtemps que je n’avais pas publié d’article sur mon blog. J’ai toutefois décidé de me remettre à l’écriture par le biais d’une brève explication sur les pointeurs sur fonction en C.

L’idée d’en parler m’est venue car, dans le cadre d’un projet en entreprise, j’ai été amené à mettre en place un protocole permettant de contrôler une application à distance via le réseau. Pour situer le contexte: l’application contrôlée est écrite en C, embarqué oblige, et elle tourne sur une carte comprenant un processeur ARM et un DSP. Pour les besoins du protocole, un daemon écoute sur une socket dédiée et reçoit de la part de clients des commandes sous la forme NOM_DE_LA COMMANDE [OPTIONS].

Le protocole en question doit pouvoir évoluer afin de prendre en charge des nouvelles commandes. Par conséquent, la contrainte que je me suis fixée est qu’il faut que l’ajout d’une nouvelle commande au protocole implique un minimum de changements au niveau du code. C’est afin de répondre à ce problème que j’ai eu recours aux pointeurs sur fonction.

Pour l’illustration, je vais donc donner un exemple, volontairement simplifié, en m’imaginant que je dois pouvoir traiter les commandes du protocole et appeler des fonctions internes à mon programme à cette fin.

En m’inspirant du design pattern “chaîne de responsabilité”, je vais écrire une fonction prenant en argument une commande du protocole avec ses options et qui se se charge d’appeler une fonction de traitement spécifique à ladite commande.

Avant de pouvoir faire cela, il est nécessaire de définir une structure contenant le nom d’une commande du protocole ainsi que sa fonction de traitement associée:

1
2
3
4
struct proto_cmd {
    const char *name;
    int (*process) (char *);
};

Si le premier élément de ma structure est un type très courant, les lecteurs non familier avec les pointeurs sur fonction sont certainement intrigués par le deuxième. Pour ceux-là, pas de panique, l’explication arrive.

Un protocole n’étant, en principe, pas constitué que d’une seule commande, il est également nécessaire d’avoir un moyen de stocker la liste des commandes. Cela peut se faire de plusieurs façons comme en utilisant le principe d’une liste chainée ou encore d’une table de hashage (bon, dans ce cas là le protocole devrait comporter un grand nombre de commandes pour que ça se justifie). Pour rester simple, je vais simplement stocker la liste des commandes dans un tableau:

1
2
3
4
5
struct proto_cmd cmd_table[NBCMD] = {
    {"quit", process_quit},
    {"start", process_start},
    {"stop", process_stop}
};

NBCMD correspond évidemment au nombre de commandes du protocole (seulement trois pour l’exemple ici).

A partir du moment où je possède une table avec la liste des commandes du protocole, il est facile d’écrire la fonction dont je vous parlais tout à l’heure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
select_cmd(char *cmd, char *args)
{
  int i;

  for (i = 0; i < NBCMD; i++)
      if (strcmp(cmd, cmd_table[i].name) == 0)
          return cmd_table[i].process(args);

    /*
     * Si on arrive la, alors cela veut dire que la commande ne fait pas partie
     * du protocole...
     */
     (void)fprintf(stderr, "Unknown command: %s\n", cmd);
     return 1;
}

La ligne 9 est celle qui revêt ici le plus d’intérêt. C’est en effet là que se fait l’appel à la fonction associée à la commande, ceci grâce au pointeur sur fonction. Il est à noter que le traitement des commandes se fait dans l’ordre des éléments du tableau. Si le protocole comporte de nombreuses commandes, il faudrait voir pour envisager une solution plus efficace.

Bien évidemment, il reste une chose à écrire: toutes les fonctions de traitement associées aux commandes. Dans ma table je les ai nommées process_quit, process_start et process_stop.

1
2
3
4
5
6
int
process_quit(char *args)
{
    /* on fait notre traitement de la commande "quit" ... */
    return 0;
}

Et ainsi de suite pour les autres commandes. Par conséquent, si l’on souhaite ajouter une commande au protocole, il suffit d’ajouter la commande en question dans la table et d’implémenter sa fonction process. Et c’est tout! Aucun autre changement n’est donc nécessaire dans le code. Sympa non?

Pour la dernière explication, je vais revenir sur la structure proto_cmd. On peut maintenant se rendre compte que, pour le deuxième élément, int correspond au type de retour de la fonction process, que (*process) correspond au pointeur sur la fonction et que (char *) s’associe à l’argument de la fonction process.

J’espère que cette brève explication illustrée était clair. Si ce n’est pas le cas, n’hésitez pas à me contacter par e-mail afin que je puisse modifier cet article en conséquence.

Les Avantages Du Libre: Une Expérience Personnelle :)

Je vous ai parlé dans mon dernier article d’un petit programme que j’ai écrit récemment et que j’ai publié sous licence BSD: dfc.

Si je vous en reparle aujourd’hui, ce n’est pas spécialement pour annoncer que le logiciel a fortement évolué depuis la dernière fois que j’en ai parlé sur ce blog (bien que ce soit le cas) mais c’est surtout que j’ai envie de parler de la bonne expérience que je suis en train de vivre grâce au libre.

Voici donc l’histoire:

Tout a commencé avec un script bash. Ce script, que m’avait fourni un de mes professeur à l’université, affichait les informations de la commande df(1) et y apportait un graphe montrant le taux d’utilisation des disques.

J’ai trouvé l’idée très sympa mais ai également trouvé le script limité et j’ai donc souhaité y ajouter des options. Or, l’état du code m’a assez vite effrayé et j’ai donc décidé de le ré-implémenter en C, langage que j’affectionne.

Je suis vite arrivé au résultat souhaité, à savoir afficher les principales informations fournies par df(1) et y ajouter un graphe. J’en ai parlé sur un forum et j’ai remarqué que les gens étaient plutôt intéressés par le concept. Puis, prenant particulièrement mon pied dans le développement de cet outil, j’ai décidé de lui ajouter des fonctionnalités et un peu d’agrément visuel par l’apport notamment de la couleur.

Après avoir amélioré ce programme, j’en ai parlé sur le forum anglophone de la distribution Archlinux. Les personnes l’ayant testé ont vite souhaité y voir plus de fonctionnalités, que je n’ai pas tardé à implémenter et ont également relevé plusieurs bugs que je me suis empressé de corriger.

J’en suis finalement arrivé à une version que je trouvais relativement mature et je me suis dit que plutôt que de garder cet outil pour moi, j’allais en parler un petit peu afin de pouvoir en faire profiter d’autres. C’est dans cet esprit que j’ai proposé une dépêche sur linuxfr dimanche passé et qui a été publiée le 1er avril (un lecteur de linuxfr a d’ailleurs cru à un poisson d’avril :P).

J’étais très loin d’imaginer l’enthousiasme que lèverait cette dépêche! Peu après la parution de l’article, j’ai été contacté par de nombreuses personnes. L’une proposant une amélioration, l’autre relevant un bug ou encore d’autres proposant des patchs (port du code vers FreeBSD notamment).

J’ai maintenant appris que dfc(1) sera vraisemblablement intégré dans les dépôts de la future Mageia, se trouve déjà dans les ports FreeBSD et s’apprête à rejoindre les dépôts de Frugalware et peut-être même ceux de Fedora!

Sincèrement, j’étais bien loin de m’imaginer tout cela lorsque j’ai commencé à taper quelques lignes de code il y a une quinzaine de jours sur mon portable…

Je souhaitais surtout relever à travers cet article le super esprit du libre qui a permis l’amélioration de dfc(1) en raisons de nombreuses contributions d’utilisateurs sous des formes variées. Et sincèrement, je souhaite tous les remercier. Bref, pour moi c’est une expérience personnelle extrêmement positive concernant les avantages du libre!