Chez Cloudflare, nous aimons Go. Nous l'utilisons dans de nombreux projets logiciels internes, ainsi que dans des systèmes de pipeline plus importants. Mais pouvons-nous franchir l'étape suivante avec Go et l’utiliser comme langage de script pour notre système d’exploitation préféré, Linux ?

gopher-tux-1
Image gopher  CC BY 3.0 Renee French Image Tux  CC0 BY OpenClipart-Vectors

Pourquoi considérer Go comme un langage de script

Réponse courte : pourquoi pas ? Go est relativement facile à apprendre, pas trop verbeux et il existe un immense diversité de bibliothèques qui peuvent être réutilisées pour éviter d'écrire tout le code à partir de zéro. D'autres avantages potentiels qu'il pourrait apporter :

  • Le système de construction basé sur Go pour votre projet Go : la commande go build convient principalement aux petits projets autonomes. Les projets plus complexes adoptent généralement un système de construction/ensemble de scripts. Pourquoi ne pas avoir ces scripts également écrits dans Go ?
  • Gestion de paquets facile et sans privilèges prête à l'emploi : si vous souhaitez utiliser une bibliothèque tierce dans votre script, vous pouvez simplement aller le chercher. Et comme le code sera installé dans votre GOPATH, l’acquisition d’une bibliothèque tierce n’exige pas de privilèges d’administrateur sur le système (contrairement à certains autres langages de script). Ceci est particulièrement utile dans les grandes entreprises.
  • Prototypage rapide de code sur les premières étapes du projet : lorsque vous écrivez la première itération du code, il faut généralement beaucoup de modifications, même pour le compiler, et vous devez gaspiller beaucoup de frappes de clavier pendant le cycle « edit-> build-> check ». Au lieu de cela, vous pouvez sauter la partie « construction » et simplement exécuter immédiatement votre fichier source.
  • Langage de script fortement typé : si vous faites une petite erreur de frappe quelque part au milieu du script, la plupart des scripts exécuteront tout jusque-là et échoueront sur la faute de frappe elle-même. Cela pourrait laisser votre système dans un état incohérent. Avec les langages fortement typés, de nombreuses fautes de frappe peuvent être interceptées au moment de la compilation, de sorte que le script contenant des bogues ne puisse pas s’exécuter en premier lieu.

État actuel du script Go

À première vue, les scripts Go semblent faciles à implémenter avec le support Unix des lignes « shebang » des scripts. Une ligne shebang est la première ligne du script, qui commence par #! et précise l'interpréteur de script à utiliser pour exécuter ce dernier (par exemple, #!/bin/bash ou #!/usr/bin/env python), afin que le système sache exactement comment exécuter le script, quel que soit le langage de programmation utilisé . Et Go prend déjà en charge l’appel de type interprète pour les fichiers .go avec la commande gorun. Il suffit donc simplement d’ajouter une ligne Shebang appropriée, quelque chose comme #!/usr/bin/env go run, à n’importe quel fichier .go, définir le bit exécutable et tout est ok.

Cependant, l'utilisation de go run directement pose des problèmes. Cet excellent article décrit en détail tous les problèmes liés à gorun et aux solutions de contournement potentielles, mais l'essentiel est :

  • gorun ne renvoie pas correctement le code d'erreur du script au système d'exploitation, ce qui est important pour les scripts, car les codes d'erreur constituent l'un des moyens les plus courants d'interaction de plusieurs scripts entre eux et avec l'environnement du système d'exploitation.
  • vous ne pouvez pas avoir une ligne shebang dans un fichier .go valide, car Go ne sait pas comment traiter les lignes commençant par #. Ce problème ne se pose pas dans les autres langages de script, parce que, pour la plupart d'entre eux,# est un moyen de préciser des commentaires. Donc, l'interprète final ignore simplement la ligne shebang, mais les commentaires Go commencent par // et gorun sur invocation produira simplement une erreur comme :
package main:
helloscript.go:1:1: illegal character U+0023 '#'

L’article décrit plusieurs solutions de contournement aux problèmes ci-dessus, notamment l’utilisation d’un programme d'emballage personnalisé gorun en tant qu'interprète, mais toutes ne constituent pas une solution idéale. Vous devez :

  • utiliser une ligne shebang non standard, commençant par //. Ceci n’est même pas techniquement une ligne shebang, mais la façon dont bash shell traite les fichiers texte exécutables. Cette solution est donc spécifique àbash. De plus, en raison du comportement spécifique de gorun, cette ligne est plutôt complexe et n’est pas évidente (voir l’article original pour des exemples).
  • devez utiliser un programme d’emballage personnalisé gorun dans la ligne shebang, ce qui fonctionne bien. Cependant, vous vous retrouvez avec des fichiers .go, qui ne sont pas compilables avec la commande standard go build en raison du caractère illégal #.

Comment Linux exécute les fichiers

OK, il semble que l’approche shebang ne nous offre pas une solution complète. Y’a-t-il autre chose que nous pourrions utiliser ? Voyons de plus près comment le noyau Linux exécute les fichiers binaires en premier lieu. Lorsque vous essayez d'exécuter un binaire/script (ou n'importe quel fichier de ce type dont le bit exécutable est défini), votre shell utilisera simplement l’appel système execve Linux en lui transmettant le chemin du système de fichiers du binaire en question, les paramètres de ligne de commande et variables d'environnement actuellement définies. Ensuite, le noyau est responsable de l'analyse appropriée du fichier et de la création d'un nouveau processus avec le code du fichier. La plupart d'entre nous savons que Linux (et de nombreux autres systèmes d'exploitation de type Unix) utilise le format binaire ELF pour ses programmes exécutables.

Toutefois, l'un des principes fondamentaux du développement du noyau Linux consiste à éviter le « verrouillage fournisseur/forma » pour tout sous-système, qui fait partie du noyau. Par conséquent, Linux implémente un système « enfichable », qui permet au noyau de prendre en charge tous les formats binaires. Il vous suffit d'écrire un module correct qui peut analyser le format de votre choix. Et si vous examinez de plus près le code source du noyau, vous verrez que Linux prend en charge davantage de formats binaires prêts à l'emploi. Par exemple, pour le récent noyau Linux 4.14, nous pouvons constater qu’il prend en charge au moins 7 formats binaires (les modules dans l’arborescence de divers formats binaires ont généralement le préfixe binfmt_ dans leurs noms). Il convient de noter le module binfmt_script. Il est responsable de l'analyse des lignes shebang mentionnées ci-dessus et de l'exécution des scripts sur le système cible. Beaucoup de gens ignorent que le support shebang est réellement implémenté dans le noyau lui même et non dans le shell ou autre démon/processus.

Extension des formats binaires pris en charge à partir de l'espace utilisateur

Mais depuis que nous avons conclu que shebang n'est pas la meilleure solution pour nos scripts Go, il semble que nous ayons besoin de quelque chose d'autre. Étonnamment, le noyau Linux a déjà un module de support binaire « quelque chose d'autre », qui porte le nom approprié binfmt_misc. Le module permet à un administrateur d’ajouter de manière dynamique une prise en charge de divers formats exécutables directement à partir de l’espace utilisateur via une interface procfs bien définie. Il est également bien documenté.

Suivons la documentation et essayons de configurer une description de format binaire pour les fichiers.go. Tout d’abord, le guide vous indique de monter le système de fichiers spécial binfmt_misc dans /proc/sys/fs/binfmt_misc. Si vous utilisez une distribution Linux relativement récente basée sur systemd, il est fort probable que le système de fichiers soit déjà installé pour vous, car systemd installe par défaut des unités spéciales de montage et de montage automatique à cette fin. Pour (re)vérifier, exécutez simplement :

$ mount | grep binfmt_misc
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=27,pgrp=1,timeout=0,minproto=5,maxproto=5,direct)

Une autre méthode consiste à vérifier si vous avez des fichiers dans /proc/sys/fs/binfmt_misc : le système de fichiers binfmt_misc correctement monté créera au moins deux fichiers spéciaux avec desregistres de noms et de statut dans ce répertoire.

Ensuite, puisque nous voulons que nos scripts .go puissent correctement transmettre le code de sortie au système d’exploitation, nous avons besoin de l’emballage gorun personnalisé en tant que notre « interprète » :

$ go get github.com/erning/gorun
$ sudo mv ~/go/bin/gorun /usr/local/bin/

Techniquement, nous n’avons pas besoin de déplacer gorun vers/usr/local/bin ou tout autre chemin système.En effet, binfmt_misc requiert de toute façon un chemin complet vers l’interpréteur, mais le système peut utiliser ce programme exécutable avec des privilèges arbitraires. Il est donc judicieux de limiter l’accès au fichier du point de vue de la sécurité.

À ce stade, créons un simple script de jouets Go helloscript.go et vérifions que nous pouvons l’« interpréter ». Le script :

package main

import (
	"fmt"
	"os"
)

func main() {
	s := "world"

	if len(os.Args) > 1 {
		s = os.Args[1]
	}

	fmt.Printf("Hello, %v!", s)
	fmt.Println("")

	if s == "fail" {
		os.Exit(30)
	}
}

Vérifier si la transmission des paramètres et la gestion des erreurs fonctionnent comme prévu :

$ gorun helloscript.go
Hello, world!
$ echo $?
0
$ gorun helloscript.go gopher
Hello, gopher!
$ echo $?
0
$ gorun helloscript.go fail
Hello, fail!
$ echo $?
30

Maintenant, nous devons dire au module binfmt_misc comment exécuter nos fichiers .go avec gorun. Selon la documentation, nous avons besoin de cette chaîne de configuration : : :golang:E::go::/usr/local/bin/gorun:OC, qui indique au système : « si vous rencontrez un fichier exécutable avec l'extension .go, veuillez exécuter avec un interpréteur /usr/local/bin/gorun ». Les indicateurs OC situés à la fin de la chaîne permettent de s'assurer que le script sera exécuté conformément aux informations du propriétaire et selon les bits d'autorisation définis sur le script lui-même, et non selon ceux définis dans le fichier binaire de l'interpréteur. Cela rend le comportement d'exécution du script Go identique au reste des programmes exécutables et des scripts sous Linux.

Enregistrons notre nouveau format binaire de script Go :

$ echo ':golang:E::go::/usr/local/bin/gorun:OC' | sudo tee /proc/sys/fs/binfmt_misc/register
:golang:E::go::/usr/local/bin/gorun:OC

Si le système a enregistré le format avec succès, un nouveau fichier golang devrait apparaître dans le répertoire /proc/sys/fs/binfmt_misc. Enfin, nous pouvons exécuter originairement nos fichiers .go:

$ chmod u+x helloscript.go
$ ./helloscript.go
Hello, world!
$ ./helloscript.go gopher
Hello, gopher!
$ ./helloscript.go fail
Hello, fail!
$ echo $?
30

C'est tout ! Nous pouvons maintenant éditer helloscript.go à notre guise et voir que les modifications seront immédiatement visibles à la prochaine exécution du fichier. De plus, contrairement à l’approche shebang précédente, nous pouvons compiler ce fichier à tout moment dans un véritable programme exécutable avec go build.


Que vous aimiez Go ou creuser dans les dossiers internes de Linux, nous avons des postes pour l'un ou l'autre et même les deux à la fois. Consulteznotre page carrières.

Linux Go Tech Talks Développeurs Programmation