Nombres décimaux et Python
Python, comme de nombreux autres langages ainsi que des implémentations matérielles, suit la norme IEEE 754 pour manipuler les nombres à virgule (le type float
en Python). Cette norme définit les tailles possibles de mémoire allouée pour contenir le nombre. La taille étant fixe, certains nombres ne sont pas représentables et la valeur enregistrée peut être légèrement erronée.
Cette situation n’est donc pas spécifique à Python. L’écart entre la valeur saisie et la valeur en mémoire est visible avec un interpréteur Python :
$ python3 -q >>> 1.9999999999999943e+71 1.9999999999999942e+71
ou un calcul qui devrait valoir 0
si les mathématiques étaient une science exacte :
$ python3 -q >>> 0.1 + 0.1 + 0.1 - 0.3 5.551115123125783e-17
Ce type d’erreur ne se rencontre pas uniquement dans les domaines spatial ou scientifique. Par exemple, des calculs de TVA et TTC peuvent produire des erreurs visibles pour l’utilisateur.
Pour éviter ces erreurs, il est possible d’utiliser la bibliothèque decimal incluse dans la bibliothèque standard :
$ python3 -q >>> from decimal import Decimal >>> decimal.Decimal('1.9999999999999943e+71') Decimal('1.9999999999999943E+71') >>> Decimal(1) / Decimal(10) + \ ... Decimal(1) / Decimal(10) + \ ... Decimal(1) / Decimal(10) - \ ... Decimal(3) / Decimal(10) Decimal('0.0')
Un autre moyen est de faire des calculs en n’utilisant que des entiers et faire des conversions au dernier moment. Dans le cas de la TVA, cela signifie de ne travailler qu’en centimes et de ne convertir en euro que lors de l’affichage à l’utilisateur (avec l’arrondi adapté, limité à deux décimales).
Références
- pour plus de détails sur IEEE 754, la page wikipedia francophone
- l’exemple de calcul provient de la documentation du module
decimal
- l’article ayant inspiré cet article
Vérification de la syntaxe de certains fichiers de configuration
Certains logiciels fournissent aussi la possibilité de vérifier la syntaxe des fichiers de configuration qu’ils utilisent. Cela permet d’éviter des erreurs ou interruptions de service dûes à une erreur dans le fichier. Voici trois exemples :
1. Apache
Apache2 fournit apachectl
. Si la syntaxe des sites actifs est correcte, la sortie sera :
# apachectl -t Syntax OK
Avec un fichier de configuration incorrect nommé conte.conf
contenant
<VirtualHost a.oree.du.bois:80> ServerName le.grand.mechant.loup.example CustomLog /il/etait/une/fois combined RencontreChaperonRouge on </VirtualHost>
la sortie sera
# apachectl -t [Sun Sep 18 22:18:32.305781 2022] [core:error] [pid 14382:tid 139846731306112] (EAI 2)Name or service not known: AH00547: Could not resolve host name a.oree.du.bois -- ignoring! AH00526: Syntax error on line 4 of /etc/apache2/sites-enabled/conte.conf: Invalid command 'RencontreChaperonRouge', perhaps misspelled or defined by a module not included in the server configuration Action '-t' failed. The Apache error log may have more information.
Attention, les vérifications d’apachectl ne sont pas exhaustives et une erreur peut encore survenir lors du redémarrage du serveur Apache. Ici le chemin vers le fichier n’existe pas mais n’a pas été détecté. Si apachectl -t
détecte une erreur, il y a un problème. S’il n’en détecte pas, il n’y a peut-être pas de problème.
(test réalisé avec Apache/2.4.38)
2. OpenSSH
La commande sshd -t
exécutée avec des droits root permet de vérifier la validité de la configuration du serveur openSSH (le fichier /etc/ssh/sshd_config
sous Debian).
Si le fichier est correct, alors rien n’est affiché et la valeur de sortie est 0.
Avec un fichier sshd_config commençant par :
PetitPotDeBeurre on Tartiflette off
La sortie sera :
# sshd -t [sudo] password for stephane: /etc/ssh/sshd_config: line 1: Bad configuration option: PetitPotDeBeurre /etc/ssh/sshd_config: line 2: Bad configuration option: Tartiflette /etc/ssh/sshd_config: terminating, 2 bad configuration options
avec une valeur de sortie de 255.
(test réalisé avec OpenSSH_7.9p1, OpenSSL 1.1.1d)
3. Sudo
Si une erreur est faite dans le fichier /etc/sudoers
qui empêche sa relecture par l’exécutable sudo
, il devient impossible d’utiliser la commande sudo
. visudo
permet d’éviter ce désagrément.
Supposons que l’utilisateur ait ajouté à la ligne 12,
Hello MereGrand
puis enregistre le fichier :
% sudo visudo /etc/sudoers:12:25: erreur de syntaxe Hello MereGrand ^ Et maintenant ?
Lorsque le fichier est incorrect, trois choix sont possibles :
- remodifier le fichier
- quitter sans enregistrer
- quitter en enregistrant (une déception pourrait arriver peu de temps après)
L’éditeur par défaut utilisé par visudo
est vi
. Cela est modifiable en paramétrant des variables d’environnement comme $EDITOR
. (En réalité, c’est plus compliqué: il y a deux autres variables d’environnement possibles et deux variables de configuration de sudo permettent de modifier de comportement des éditeurs par défaut. man sudo
si vous pensez que cette complexité a un intérêt dans votre cas.)
(testé avec visudo version 1.9.5p2, version de la grammaire de visudo : 48)
Faim de loup, fin d’article
Ces outils sont pratiques pour éviter de mettre un service en panne ou s’enfermer dehors. Ils sont complémentaires de vérificateur générique de syntaxe JSON, YAML, etc.
Hello Debian en Brainfuck
screenshots.debian.net est un service qui permet d’afficher des captures d’écran de logiciels. C’est assez pratique pour se faire une idée d’une interface par exemple. Une capture d’écran montrait déjà l’interpréteur Brainfuck beef affichant un classique
Hello Word!
. Mais on peut aussi personnaliser en affichant un
Hello Debian!
:
Brainfuck
Brainfuck est un langage dont l’intérêt principal est d’être difficilement compréhensible par un humain. Pas la peine de s’étendre sur ses spécificités, wikipedia le fait très bien. Il ressemble à une machine de Turing: le programme déplace un curseur dans un tableau et modifie les valeurs contenues dans les cellules du tableau.
Voici une version commentée du programme utilisé (le début est quasi-identique au hello world
fourni sur la page wikipedia puisqu’on veut écrire la même chose) :
++++++++++ affecte 10 à la case 0 [ boucle initialisant des valeurs au tableau > avance à la case 1 +++++++ affecte 7 à la case 1 > avance à la case 2 ++++++++++ affecte 10 à la case 2 > avance à la case 3 +++ affecte 3 à la case 3 > avance à la case 4 + affecte 1 à la case 4 > avance à la case 5 +++++++++++ affecte 11 à la case 5 <<<<< retourne à la case 0 - enlève 1 à la case 0 ] jusqu'à ce que la case 0 soit = à 0
La boucle initialise le tableau en 10 itérations et son état est alors :
Case | 0 | 1 | 2 | 3 | 4 | 5 |
Valeur | 0 | 70 | 100 | 30 | 10 | 110 |
Suite du programme :
>++ ajoute 2 à la case 1 (70 plus 2 = 72) . imprime le caractère 'H' (72) >+ ajoute 1 à la case 2 (100 plus 1 = 101) . imprime le caractère 'e' (101) +++++++ ajoute 7 à la case 2 (101 plus 7 = 108) . imprime le caractère 'l' (108) . imprime le caractère 'l' (108) +++ ajoute 3 à la case 2 (108 plus 3 = 111) . imprime le caractère 'o' (111) >++ ajoute 2 à la case 3 (30 plus 2 = 32) . imprime le caractère ' '(espace) (32) <<< revient à la case 0 ++ ajoute 2 à la case 0 (0 plus 2 = 2) [ une boucle > avance à la case 1 -- enlève 4 à la case 1 (72 moins 4 = 68) > avance à la case 2 ----- enlève 10 à la case 2 (111 moins 10 = 101) << retourne à la case 0 - enlève 1 à la case 0 ] jusqu'à ce que la case 0 soit = à 0 > va case 1 . affiche 'D' > va case 2 . affiche 'e' --- enlève 3 à la case 2 (101 moins 3 = 98) . affiche 'b' >>> va case 5 ----- enlève 5 à la case 5 . affiche 'i' <<< va case 2 - enlève 1 à la case 2 . affiche 'a' >>> va case 5 +++++ ajoute 5 à la case 5 . affiche 'n' << va à la case 3 + ajoute 1 à la case 3 . affiche un point d'exclamation > va à la case 4 . imprime le caractère 'nouvelle ligne' (10)
screenshots.debian.net
Une capture de l’exécution du programme est disponible pour les interpréteurs beef et hsbrainfuck sur screenshot.debian.net.
Les images disponibles sur screenshots.debian.net sont aussi réutilisées par le service packages.debian.org (par exemple packages.debian.org) et par certains gestionnaires de paquets.
Si vous avez envie d’ajouter des captures d’écran à des paquets qui n’en auraient pas (les plus courants sont déjà faits), sachez que l’affichage n’est pas direct car il y a une validation manuelle des images envoyées. Le délai reste limité à quelques jours (voire à la journée).
Évolution de la difficulté du minage de Bitcoin
Le Bitcoin est une monnaie cryptographique dont la validation des transactions est faite par une preuve de travail. La preuve de travail consiste à trouver un condensat (hash) commençant par un certain nombre de zéros.
Nombre de zéro au début du hash d’une transaction
Un hash de transaction (Transaction Hash ID) ressemble, par exemple, à 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
. Dans ce cas, 10 zéros débutent le hash.
Plus le nombre de zéro exigé est élevé, plus il est difficile de trouver une solution. Ce nombre évolue au fil du temps : si une solution est trouvée rapidement, le nombre de zéro augmente pour les transactions suivantes (et inversement).
Évolution jusqu’à aujourd’hui
En bleu, les statistiques de l’ensemble des transactions contenues dans la chaîne. En orange, la moyenne des transactions de chaque journée. Les points noirs représentent les moyennes mensuelles.
La difficulté est croissante, avec parfois des plateaux. Cela montre indirectement l’augmentation de la puissance de calcul au fil du temps (le nombre de machines minant augmente, les processeurs sont plus performants, etc.).
Cela mesure encore plus imparfaitement la pollution générée par l’utilisation :
- d’énergies renouvelables : à une période où le prix du Bitcoin était faible, une stratégie de minimisation des coûts a poussé certains à se limiter à la consommation d’excédents d’énergie hydroélectrique ou éolienne.
- d’énergie fossile : le prix actuel pousse à la relance de centrales à gaz ou à charbon.
Production du graphique
Il est possible de voir les informations d’une transaction sur des services web mais autant extraire les informations à partir de la chaîne Bitcoin avec le client grâce à un script shell suivant :
#! /bin/sh MAX_BLOCK=$(./bin/bitcoin-cli getblockcount) for i in $(seq 1 $MAX_BLOCK); do { HASH=$(./bin/bitcoin-cli getblockhash $i) ./bin/bitcoin-cli getblock $HASH | jq '[.time, .hash] | @csv' | sed 's/[\"]//g' >> stats_brutes.csv } done
Le script nécessite que la chaîne complète soit disponible sur la machine. La chaîne consomme presque 400 Go d’espace disque. La version du client et serveur était la 0.21.1.
Les commandes du binaire bitcoin-cli
sont documentées sur https://developer.bitcoin.org/reference/rpc/index.html.
La production des statistiques au format .csv est très loooongue car le client bitcoin met du temps à répondre.
Une fois les données extraites au format .csv, un script python utilise la bibliothèque matplotlib pour produire le graphique :
import csv import datetime import time import matplotlib.pyplot as plt import matplotlib.dates as mdates import numpy as np def draw_graph(csv_path): SIZE = 0.5 stats_completes, stats_quotidiennes, stats_mensuelles = stats(csv_path) fig, ax = plt.subplots() ax.plot_date(stats_completes["x"], stats_completes["y"], markersize=SIZE, label="Tous", color="blue") ax.plot_date(stats_quotidiennes["x"], stats_quotidiennes["y"], markersize=SIZE*2, label="Quotidien", color="orangered") ax.plot_date(stats_mensuelles["x"], stats_mensuelles["y"], markersize=SIZE*2, label="Mensuel", color="black") fmt_year = mdates.YearLocator() ax.xaxis.set_major_locator(fmt_year) fmt_month = mdates.MonthLocator() ax.xaxis.set_minor_locator(fmt_month) ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) ax.fmt_xdata = mdates.DateFormatter('%Y-%m-%d %H:%M:%S') fig.autofmt_xdate() ax.grid(True) plt.xlabel("Temps") plt.ylabel("Nombre de zéros") plt.legend(loc="upper left", markerscale=4) plt.savefig("./graph_bitcoin_zeros.png", dpi=150) def stats(path): x_toutes, y_toutes = [], [] with open(path, newline='') as csv_file: stats_reader = csv.reader(csv_file) for ligne in stats_reader: date = _from_timestamp(ligne[0]) nb_zeros = _nb_zeros(ligne[1]) x_toutes.append(date) y_toutes.append(nb_zeros) stats_quotidiennes = _stats_quotidiennes(x_toutes, y_toutes) stats_mensuelles = _stats_mensuelles(x_toutes, y_toutes) return {"x": x_toutes, "y": y_toutes}, stats_quotidiennes, stats_mensuelles def _stats_quotidiennes(x_toutes, y_toutes): dates_possibles = set([(date.year, date.month, date.day) for date in x_toutes]) stats_quot = {date: [] for date in dates_possibles} for index, x in enumerate(x_toutes): stats_quot[x.year, x.month, x.day].append(y_toutes[index]) x, y = [], [] for mois in sorted(dates_possibles): x.append(datetime.datetime(mois[0], mois[1], mois[2])) y.append(_moyenne(stats_quot[mois])) return {"x": x, "y": y} def _stats_mensuelles(x_toutes, y_toutes): dates_possibles = set([(date.year, date.month) for date in x_toutes]) stats_mois = {date: [] for date in dates_possibles} for index, x in enumerate(x_toutes): stats_mois[x.year, x.month].append(y_toutes[index]) x, y = [], [] for date in sorted(dates_possibles): x.append(datetime.datetime(date[0], date[1], 15)) y.append(_moyenne(stats_mois[date])) return {"x": x, "y": y} def _moyenne(valeurs): return sum(valeurs) / len(valeurs) def _from_timestamp(data): return datetime.datetime.fromtimestamp(int(data)) def _nb_zeros(data): count = 0 for lettre in data: if lettre == "0": count += 1 else: return count if __name__ == "__main__": draw_graph("stats_brutes.csv")
Le script python utilise un type de graphique idéal pour le besoin, déjà inclus dans Matplotlib. 🙂
Démontage WD Elements Play
WD Elements Play était un boîtier multimédia vendu par Western Digital à partir des années 2010. Apparemment c’était un système assez limité dès sa sortie. Il semble que Western Digital ait simplement utilisé une conception faite chez Amlogic, en y incluant sa propre production de disque dur.
Composants
Le démontage du boîtier est aisé : il faut retourner le boîtier et déclipser le fond à l’aide d’un tournevis plat. Le disque dur se retire directement en le reculant pour le déconnecter du connecteur SATA, puis un deuxième cran pour pouvoir le sortir du boîtier. Une vidéo youtube faite par quelqu’un d’autre montre ce processus.
La carte qui fait l’intermédiaire entre les Entrées/Sorties et le disque dur est basée sur une puce Amlogic AML8626-H aux spécifications mystérieuses.
Le disque dur est au format 3,5 pouces et d’une taille de 1To. Sur le côté du disque, on peut voir les vis, chacune avec une pièce en caoutchouc pour retenir le disque dans le boîtier.
Quel format pour le disque ?
Étant donné qu’il s’agit d’un disque standard, il suffit de le brancher sur une machine quelconque pour le détecter (ici sur /dev/sdb
) :
# fdisk -l [...] Disk /dev/sdb: 931,51 GiB, 1000204886016 bytes, 1953525168 sectors Disk model: WDC WD10EADS-11M Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 4096 bytes I/O size (minimum/optimal): 4096 bytes / 4096 bytes Disklabel type: dos Disk identifier: 0x9a6027cc Device Boot Start End Sectors Size Id Type /dev/sdb1 2048 1953521663 1953519616 931,5G 27 Hidden NTFS WinRE
Parted est d’accord avec fdisk :
# parted -l [...] Model: ATA WDC WD10EADS-11M (scsi) Disk /dev/sdb: 1000GB Sector size (logical/physical): 512B/4096B Partition Table: msdos Disk Flags: Number Start End Size Type File system Flags 1 1049kB 1000GB 1000GB primary ntfs msftres
L’équivalent graphique avec gparted
Il y a donc une partition unique, au format Microsoft NTFS. L’intérêt est probablement d’avoir un format reconnu nativement par des machines Windows car il est possible de brancher le boîtier en USB pour le relier à un ordinateur.
Regarder le contenu du disque est trivial :
# mkdir /tmp/ntfs # mount -t ntfs /dev/sdb1 /tmp/ntfs
ls /tmp/ntfs
montre des fichiers comme la corbeille (RECYCLER
), un répertoire autorun
et un fichier autorun.inf
(probablement utilisé lors du branchement en USB du boîtier sur un système Windows), un répertoire System Volume Information
avec un point de restauration (peut-être pour une réinitialisation usine?) et un répertoire fourni par WD contenant un peu de documentation au format pdf. Bien évidemment, on y retrouve aussi les fichiers déposés par l’utilisateur.
Les versions des logiciels qui n’auront pas été maltraités malgré une partition NTFS:
- fdisk et mount(util-linux 2.36.1)
- parted (GNU parted) 3.4
- GParted 1.2.0