GNU/Linux >> Tutoriels Linux >  >> Linux

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

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.


Linux
  1. Boîte occupée lire le fichier ligne par ligne ?

  2. Tail lit-il tout le fichier ?

  3. Trouver N mots les plus fréquents dans un fichier avec une liste de mots vides à partir de la ligne de commande ?

  4. Pourquoi le descripteur de fichier est-il ouvert et lu une seule fois ?

  5. Comment supprimer X octets à la fin d'un gros fichier sans lire tout le fichier ?

Comment lire des fichiers ligne par ligne dans Bash

Comment lire un fichier ligne par ligne dans Bash

Comment effacer le contenu d'un fichier depuis la ligne de commande ?

Comment obtenir l'URL du fichier Dropbox à partir de la ligne de commande ?

Linux - grep de certaines lignes à la fin du fichier

Pourquoi rsync ne parvient-il pas à copier les fichiers de /sys sous Linux ?