Perché alcune shell `read` builtin non riescono a leggere l'intera riga dal file in` / proc`?


19

In alcuni Bourne-come gusci, la readfunzione interna non può leggere tutta la linea da file in /proc(il seguente comando dovrebbe essere eseguito in zsh, sostituire $=shellcon $shellcon altre shell):

$ 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

readlo standard richiede che lo standard input sia un file di testo , tale requisito causa comportamenti diversi?


Leggi la definizione POSIX del file di testo , faccio qualche verifica:

$ 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

Non ci sono NULpersonaggi nel contenuto di /proc/sys/fs/file-max, e anche findriportato come un file normale (è presente un bug find?).

Immagino che la shell abbia fatto qualcosa sotto il cofano, come file:

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty

Risposte:


31

Il problema è che quei /procfile su Linux appaiono come file di testo per quanto stat()/fstat()riguarda, ma non si comportano come tali.

Poiché si tratta di dati dinamici, è possibile effettuare solo una read()chiamata di sistema (almeno per alcuni di essi). Fare più di uno potrebbe procurarti due blocchi di due contenuti diversi, quindi invece un secondo read()su di essi non restituisce nulla (che significa fine del file) (a meno che non lseek()torni all'inizio (e solo all'inizio)).

L' readutilità deve leggere il contenuto dei file un byte alla volta per assicurarsi di non leggere oltre il carattere di nuova riga. Ecco cosa dashfa:

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

Alcune shell come bashhanno un'ottimizzazione per evitare di dover fare così tante read()chiamate di sistema. Prima controllano se il file è ricercabile e, in tal caso, leggono in blocchi poiché sanno che possono riposizionare il cursore subito dopo la nuova riga se hanno letto oltre:

$ 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

Con bash, avresti ancora problemi per i file proc che sono più grandi di 128 byte e possono essere letti solo in una chiamata di sistema di lettura.

bashsembra anche disabilitare tale ottimizzazione quando -dviene utilizzata l' opzione.

ksh93porta l'ottimizzazione ancora di più a diventare fasullo. ksh93 readcerca indietro, ma ricorda i dati extra che ha letto per il prossimo read, quindi il successivo read(o uno qualsiasi degli altri suoi builtin che leggono dati come cato head) non prova nemmeno readi dati (anche se quei dati sono stati modificati da altri comandi tra):

$ 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

Ah sì, una stracespiegazione basata è molto più facile da capire!
Stephen Kitt,

Grazie, i dati dinamici hanno un senso. In che modo la shell rileva i suoi dati dinamici? Se lo faccio cat /proc/sys/fs/file-max | ..., il problema è andato.
cuonglm,

3
La shell non lo rileva. Il fatto che si procfstratti di dati dinamici significa che non è possibile gestire più read(2)chiamate successive allo stesso file; il comportamento non dipende dalla shell. L'uso cate il piping funzionano perché catlegge il file in blocchi abbastanza grandi; l' readintegrato della shell quindi legge dalla pipe un personaggio alla volta.
Stephen Kitt,

1
C'è un po 'di soluzione sporca in mksh. read -N 10 a < /proc/sys/fs/file-max
Ipor Sircer,

1
@IporSircer. Infatti. Una soluzione simile sembra funzionare con zsh: read -u0 -k10(o usa sysread; $mapfile[/proc/sys/fs/file-max]non funziona in quanto quei file non possono essere modificati mmap). In ogni caso, con qualsiasi shell, si può sempre a=$(cat /proc/sys/fs/file-max). Con alcuni inclusi mksh, zshe ksh93, a=$(</proc/sys/fs/file-max)funziona anche e non esegue il fork di un processo per eseguire la lettura.
Stéphane Chazelas,

9

Se sei interessato a sapere perché? è così, puoi vedere la risposta nei sorgenti del kernel qui :

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

Fondamentalmente, la ricerca ( *pposnon 0) non è implementata per reads ( !write) di valori sysctl che sono numeri. Ogni volta che viene eseguita una lettura /proc/sys/fs/file-max, la routine in questione __do_proc_doulongvec_minmax()viene richiamata dalla voce file-maxnella tabella di configurazione nello stesso file.

Altre voci, come ad esempio, /proc/sys/kernel/poweroff_cmdsono implementate tramite il proc_dostring()quale consente le ricerche, in modo da poterlo fare dd bs=1e leggere dalla shell senza problemi.

Si noti che dal momento che la maggior parte delle /procletture del kernel 2.6 sono state implementate tramite una nuova API chiamata seq_file e questo cerca supporti, ad esempio la lettura /proc/statnon dovrebbe causare problemi. L' /proc/sys/implementazione, come possiamo vedere, non utilizza questa API.


3

Al primo tentativo, questo sembra un bug nelle shell che restituiscono meno di una vera Bourne Shell o dei suoi derivati ​​(sh, bosh, ksh, cimelio).

La Bourne Shell originale tenta di leggere un blocco (64 byte) le nuove varianti della Bourne Shell leggono 128 byte, ma ricominciano a leggere se non è presente un nuovo carattere di riga.

Background: / procfs e implementazioni simili (ad es. Il /etc/mtabfile virtuale montato ) hanno contenuto dinamico e una stat()chiamata non causa prima la ricostruzione del contenuto dinamico. Per questo motivo, la dimensione di tale file (dalla lettura fino a EOF) può differire da ciò che stat()restituisce.

Dato che lo standard POSIX richiede che le utility si aspettino letture brevi in qualsiasi momento, il software che ritiene che un valore read()restituito inferiore alla quantità ordinata di byte sia un'indicazione EOF viene interrotto. Un'utilità correttamente implementata chiama read()una seconda volta nel caso in cui restituisca meno del previsto, fino a quando non viene restituito uno 0. Nel caso del readincorporata, sarebbe ovviamente sufficiente leggere fino EOF o fino a quando un NLè visto.

Se esegui trusso un clone di truss, dovresti essere in grado di verificare quel comportamento errato per le shell che ritornano solo 6nell'esperimento.

In questo caso speciale, sembra essere un bug del kernel Linux, vedi:

$ 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).

Il kernel Linux restituisce 0 con il secondo reade questo è ovviamente errato.

Conclusione: le shell che tentano innanzitutto di leggere una quantità abbastanza grande di dati non attivano questo bug del kernel Linux.


OK, risposta uscita con una nuova verifica per un bug del kernel Linux.
schily,

Non è un bug, è una funzione!
Guntram Blohm sostiene Monica

Questa è un'affermazione davvero strana.
schily,

Sarebbe una caratteristica se fosse documentata. Leggendo kernel.org/doc/Documentation/filesystems/proc.txt , non vedo documentazione per il comportamento. Detto questo, funziona chiaramente come previsto dall'implementatore, quindi se questo deve essere considerato un bug, è un bug nella progettazione, non nell'implementazione.
Charles Duffy,

0

I file in / proc a volte usano il carattere NULL per separare i campi all'interno del file. Sembra che leggere non sia in grado di gestirlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.