Le problème est que ces /proc
les fichiers sous Linux apparaissent comme des fichiers texte jusqu'à stat()/fstat()
est concerné, mais ne vous comportez pas comme tel.
Comme il s'agit de données dynamiques, vous ne pouvez faire qu'un seul read()
appel système sur eux (pour certains d'entre eux au moins). En faire plus d'un pourrait vous donner deux morceaux de deux contenus différents, donc à la place, il semble qu'il s'agisse d'un deuxième read()
sur eux ne renvoie rien (c'est-à-dire la fin du fichier) (sauf si vous lseek()
retour au début (et au début seulement)).
Le read
L'utilitaire doit lire le contenu des fichiers un octet à la fois pour être sûr de ne pas lire au-delà du caractère de saut de ligne. C'est ce que dash
fait :
$ strace -fe read dash -c 'read a < /proc/sys/fs/file-max'
read(0, "1", 1) = 1
read(0, "", 1) = 0
Certains shells comme bash
avoir une optimisation pour éviter d'avoir à faire autant de read()
appels système. Ils vérifient d'abord si le fichier est recherchable, et si c'est le cas, lisent en morceaux car ils savent alors qu'ils peuvent remettre le curseur juste après la nouvelle ligne s'ils l'ont lu :
$ strace -e lseek,read bash -c 'read a' < /proc/sys/fs/file-max
lseek(0, 0, SEEK_CUR) = 0
read(0, "1628689\n", 128) = 8
Avec bash
, vous auriez toujours des problèmes pour les fichiers proc de plus de 128 octets et ne pouvant être lus que dans un seul appel système en lecture.
bash
semble également désactiver cette optimisation lorsque le -d
option est utilisée.
ksh93
pousse l'optimisation encore plus loin au point de devenir bidon. read
de ksh93 recherche en arrière, mais se souvient des données supplémentaires qu'il a lues pour le prochain read
, donc le prochain read
(ou l'un de ses autres éléments intégrés qui lisent des données comme cat
ou head
) n'essaie même pas de read
les données (même si ces données ont été modifiées par d'autres commandes entre-temps) :
$ seq 10 > a; ksh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 2
$ seq 10 > a; sh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 st
Si vous souhaitez savoir pourquoi ? c'est le cas, vous pouvez voir la réponse dans les sources du noyau ici :
if (!data || !table->maxlen || !*lenp || (*ppos && !write)) {
*lenp = 0;
return 0;
}
En gros, chercher (*ppos
pas 0) n'est pas implémenté pour les lectures (!write
) de valeurs sysctl qui sont des nombres. Chaque fois qu'une lecture est effectuée à partir de /proc/sys/fs/file-max
,la routine en question__do_proc_doulongvec_minmax()
est appelé depuis l'entrée pour file-max
dans la table de configuration dans le même fichier.
Autres entrées, telles que /proc/sys/kernel/poweroff_cmd
sont implémentés via proc_dostring()
qui autorise les recherches, vous pouvez donc faire dd bs=1
dessus et lisez depuis votre shell sans problème.
Notez que depuis le noyau 2.6, la plupart des /proc
les lectures ont été implémentées via une nouvelle API appelée seq_file et cela prend en charge les recherches, par exemple la lecture de /proc/stat
ne devrait pas causer de problèmes. Le /proc/sys/
l'implémentation, comme nous pouvons le voir, n'utilise pas cette API.
À la première tentative, cela ressemble à un bogue dans les shells qui renvoient moins qu'un vrai Bourne Shell ou ses dérivés (sh, bosh, ksh, heirloom).
Le Bourne Shell d'origine essaie de lire un bloc (64 octets). Les nouvelles variantes du Bourne Shell lisent 128 octets, mais elles recommencent à lire s'il n'y a pas de nouveau caractère de ligne.
Contexte :/procfs et implémentations similaires (par exemple, le /etc/mtab
monté fichier virtuel) ont un contenu dynamique et un stat()
l'appel ne provoque pas la recréation du contenu dynamique en premier. Pour cette raison, la taille d'un tel fichier (de la lecture jusqu'à EOF) peut différer de ce que stat()
renvoie.
Étant donné que la norme POSIX exige que les utilitaires s'attendent à des lectures courtes à tout moment, un logiciel qui croit qu'un read()
qui renvoie moins que le commandé nombre d'octets est une indication EOF sont rompus. Un utilitaire correctement implémenté appelle read()
une deuxième fois au cas où il renvoie moins que prévu - jusqu'à ce qu'un 0 soit renvoyé. Dans le cas du read
builtin, il suffirait bien sûr de lire jusqu'à EOF
ou jusqu'à un NL
est vu.
Si vous exécutez truss
ou un clone de treillis, vous devriez pouvoir vérifier ce comportement incorrect pour les shells qui ne renvoient que 6
dans votre expérience.
Dans ce cas particulier, il semble s'agir d'un bogue du noyau Linux, voir :
$ sdd -debug bs=1 if= /proc/sys/fs/file-max
Simple copy ...
readbuf (3, 12AC000, 1) = 1
writebuf (1, 12AC000, 1)
8readbuf (3, 12AC000, 1) = 0
sdd: Read 1 records + 0 bytes (total of 1 bytes = 0.00k).
sdd: Wrote 1 records + 0 bytes (total of 1 bytes = 0.00k).
Le noyau Linux renvoie 0 avec le deuxième read
et c'est bien sûr incorrect.
Conclusion :les shells qui tentent d'abord de lire une quantité de données suffisamment importante ne déclenchent pas ce bogue du noyau Linux.