Pourquoi certains shells intégrés `read` ne parviennent pas à lire toute la ligne du fichier dans` / proc`?

19

Dans certains shells de type Bourne, le readbuiltin ne peut pas lire toute la ligne à partir du fichier /proc(la commande ci-dessous doit être exécutée zsh, remplacez-la $=shellpar $shelld'autres shells):

$ for shell in bash dash ksh mksh yash zsh schily-sh heirloom-sh "busybox sh"; do
  printf '[%s]\n' "$shell"
  $=shell -c 'IFS= read x </proc/sys/fs/file-max; echo "$x"'       
done
[bash]
602160
[dash]
6
[ksh]
602160
[mksh]
6
[yash]
6
[zsh]
6
[schily-sh]
602160
[heirloom-sh]
602160
[busybox sh]
6

readla norme requiert que l'entrée standard soit un fichier texte , cette exigence provoque-t-elle des comportements variés?


Lisez la définition POSIX du fichier texte , je fais quelques vérifications:

$ od -t a </proc/sys/fs/file-max 
0000000   6   0   2   1   6   0  nl
0000007

$ find /proc/sys/fs -type f -name 'file-max'
/proc/sys/fs/file-max

Il n'y a aucun NULcaractère dans le contenu de /proc/sys/fs/file-max, et l'a également findsignalé en tant que fichier normal (est-ce un bogue dans find?).

Je suppose que la coquille a fait quelque chose sous le capot, comme file:

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty
cuonglm
la source

Réponses:

31

Le problème est que ces /procfichiers sous Linux apparaissent en tant que fichiers texte en ce qui stat()/fstat()concerne, mais ne se comportent pas comme tels.

Parce que ce sont des données dynamiques, vous ne pouvez faire qu'un seul read()appel système (pour certains d'entre eux au moins). Faire plus d'un pourrait vous donner deux morceaux de deux contenus différents, donc il semble plutôt qu'une seconde read()sur eux ne retourne rien (ce qui signifie fin de fichier) (sauf si vous lseek()revenez au début (et au début uniquement)).

L' readutilitaire doit lire le contenu des fichiers un octet à la fois pour être sûr de ne pas lire au-delà du caractère de nouvelle ligne. C'est ce qui dashfait:

 $ strace -fe read dash -c 'read a < /proc/sys/fs/file-max'
 read(0, "1", 1)                         = 1
 read(0, "", 1)                          = 0

Certains shells comme bashont une optimisation pour éviter d'avoir à faire autant d' read()appels système. Ils vérifient d'abord si le fichier est recherchable, et si c'est le cas, lisent par morceaux, car ils savent 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 dont la taille dépasse 128 octets et qui ne peuvent être lus qu'en un seul appel système en lecture.

bashsemble également désactiver cette optimisation lorsque l' -doption est utilisée.

ksh93pousse l'optimisation encore plus loin au point de devenir fausse. ksh93 readrecherche, mais se souvient des données supplémentaires qu'il a lues pour la prochaine read, de sorte que la prochaine read(ou l'une de ses autres commandes intégrées qui lisent des données comme catou head) n'essaye même pas readles données (même si ces données ont été modifiées par d'autres commandes entre les deux):

$ 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
Stéphane Chazelas
la source
Ah oui, une straceexplication à base est beaucoup plus facile à comprendre!
Stephen Kitt
Merci, les données dynamiques ont du sens. Alors, comment le shell détecte-t-il ses données dynamiques? Si je le fais cat /proc/sys/fs/file-max | ..., le problème a disparu.
cuonglm
3
Le shell ne le détecte pas. Le fait qu'il s'agisse de données dynamiques signifie qu'il procfsne peut pas gérer plusieurs read(2)appels successifs vers le même fichier; le comportement ne dépend pas du shell. L'utilisation catet la tuyauterie fonctionnent car catlit le fichier en morceaux assez grands; le shell readintégré lit ensuite dans le tube un caractère à la fois.
Stephen Kitt
1
Il existe une solution de contournement un peu sale mksh. read -N 10 a < /proc/sys/fs/file-max
Ipor Sircer
1
@IporSircer. En effet. Un travail similaire semble fonctionner avec zsh: read -u0 -k10(ou utiliser sysread; $mapfile[/proc/sys/fs/file-max]ne fonctionne pas car ces fichiers ne peuvent pas être mmapédités). Dans tous les cas, avec n'importe quel shell, on peut toujours le faire a=$(cat /proc/sys/fs/file-max). Avec certains, y compris mksh, zshet ksh93, a=$(</proc/sys/fs/file-max)fonctionne également et ne bifurque pas un processus pour faire la lecture.
Stéphane Chazelas
9

Si vous souhaitez savoir pourquoi? c'est ainsi, vous pouvez voir la réponse dans les sources du noyau ici :

    if (!data || !table->maxlen || !*lenp || (*ppos && !write)) {
            *lenp = 0;
            return 0;
    }

Fondamentalement, la recherche ( *ppospas 0) n'est pas implémentée pour les lectures ( !write) des 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ée à partir de l'entrée pour file-maxdans la table de configuration dans le même fichier.

D'autres entrées, telles que celles /proc/sys/kernel/poweroff_cmdimplémentées via proc_dostring()qui permettent les recherches, vous permettent de le faire dd bs=1et de lire à partir de votre shell sans problème.

Notez que depuis le noyau 2.6, la plupart des /proclectures ont été implémentées via une nouvelle API appelée seq_file et que cela prend en charge la recherche, par exemple, la lecture /proc/statne devrait pas poser de problème. L' /proc/sys/implémentation, comme nous pouvons le voir, n'utilise pas cette API.

meuh
la source
3

À la première tentative, cela ressemble à un bug dans les shells qui retournent moins qu'un vrai Bourne Shell ou ses dérivés reviennent (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 la lecture s'il n'y a pas de nouveau caractère de ligne.

Contexte: / procfs et les implémentations similaires (par exemple le /etc/mtabfichier virtuel monté ) ont un contenu dynamique et un stat()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'à l'EOF) peut différer de ce qui est stat()renvoyé.

Étant donné que la norme POSIX oblige les utilitaires à s'attendre à de courtes lectures à tout moment, les logiciels qui croient read()qu'une valeur renvoyant moins que la quantité d'octets ordonnée est une indication EOF sont rompus. Un utilitaire correctement implémenté appelle read()une deuxième fois dans le cas où il renvoie moins que prévu - jusqu'à ce qu'un 0 soit renvoyé. En cas de readbuiltin, il serait bien sûr suffisant pour lire jusqu'à ce que EOF ou jusqu'à ce qu'un NLest vu.

Si vous exécutez trussou un clone de ferme, vous devriez pouvoir vérifier ce comportement incorrect pour les shells qui ne reviennent que 6dans 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 second readet c'est bien sûr incorrect.

Conclusion: les shells qui essaient d'abord de lire un bloc de données suffisamment volumineux ne déclenchent pas ce bogue du noyau Linux.

schily
la source
OK, sortie de la réponse avec une nouvelle vérification pour un bogue du noyau Linux.
schily
Ce n'est pas un bug, c'est une fonctionnalité!
Guntram Blohm soutient Monica
C'est une affirmation vraiment étrange.
schily
Ce serait une fonctionnalité si elle était documentée. En lisant kernel.org/doc/Documentation/filesystems/proc.txt , je ne vois aucune documentation pour le comportement. Cela dit, cela fonctionne clairement comme prévu par l'implémenteur, donc si cela doit être considéré comme un bug, c'est un bug dans la conception, pas dans l'implémentation.
Charles Duffy
0

Les fichiers sous / proc utilisent parfois le caractère NULL pour séparer les champs à l'intérieur du fichier. Il semble que la lecture ne soit pas en mesure de gérer cela.

Tony George
la source