Por que alguns shells `read` internos não conseguem ler toda a linha do arquivo em` / proc`?

19

Em alguns Bourne-como conchas, o readembutida não pode ler toda a linha a partir do arquivo em /proc(o comando a seguir devem ser executados em zsh, substitua $=shellcom $shellcom outras conchas):

$ 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

readpadrão exige que a entrada padrão precise ser um arquivo de texto ; esse requisito causa comportamentos variados?


Leia a definição POSIX do arquivo de texto , faço algumas verificações:

$ 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

Não há NULcaractere no conteúdo de /proc/sys/fs/file-max, e também o findrelatou como um arquivo regular (isso é um bug find?).

Eu acho que a concha fez algo sob o capô, como file:

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

Respostas:

31

O problema é que esses /procarquivos no Linux aparecem como arquivos de texto no que diz stat()/fstat()respeito, mas não se comportam como tal.

Como são dados dinâmicos, você pode fazer apenas uma read()chamada de sistema (para alguns deles, pelo menos). Ao fazer mais de um, você pode obter dois pedaços de dois conteúdos diferentes; portanto, parece que um segundo read()não retorna nada (o que significa fim de arquivo) (a menos que você lseek()volte ao início (e apenas ao começo)).

O readutilitário precisa ler o conteúdo dos arquivos, um byte de cada vez, para garantir a leitura do caractere de nova linha. É o que dashfaz:

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

Alguns shells bashtêm uma otimização para evitar ter que fazer tantas read()chamadas de sistema. Eles primeiro verificam se o arquivo pode ser procurado e, em caso afirmativo, leem em partes, pois sabem que podem colocar o cursor de volta logo após a nova linha, se tiver lido após:

$ 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

Com bash, você ainda terá problemas para arquivos proc com mais de 128 bytes de tamanho e que só podem ser lidos em uma chamada de sistema de leitura.

bashtambém parece desativar essa otimização quando a -dopção é usada.

ksh93leva a otimização ainda mais longe a ponto de se tornar falsa. O ksh93's readbusca de volta, mas se lembra dos dados extras que leu para o próximo read, para que o próximo read(ou qualquer um de seus outros componentes internos que leem dados como catou head) nem tente reados dados (mesmo que esses dados tenham sido modificados por outros comandos no meio):

$ 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
fonte
Ah, sim, uma straceexplicação baseada em é muito mais fácil de entender!
Stephen Kitt
Obrigado, dados dinâmicos fazem sentido. Então, como o shell detecta seus dados dinâmicos? Se eu fizer cat /proc/sys/fs/file-max | ..., o problema se foi.
cuonglm
3
O shell não o detecta. O fato de serem dados dinâmicos significa que procfsnão é possível lidar com várias read(2)chamadas sucessivas para o mesmo arquivo; o comportamento não depende do shell. Usar cate canalizar funciona porque catlê o arquivo em pedaços grandes o suficiente; o readbuilt-in do shell então lê do pipe um caractere de cada vez.
Stephen Kitt
1
Existe uma solução alternativa um pouco suja mksh. read -N 10 a < /proc/sys/fs/file-max
IPOR Sircer
1
@IporSircer. De fato. Uma solução semelhante parece funcionar com zsh: read -u0 -k10(ou use sysread; $mapfile[/proc/sys/fs/file-max]não funciona porque esses arquivos não podem ser mmapeditados). Em qualquer caso, com qualquer shell, sempre é possível a=$(cat /proc/sys/fs/file-max). Com alguns incluindo mksh, zshe ksh93, a=$(</proc/sys/fs/file-max)também funciona e não bifurca um processo para fazer a leitura.
Stéphane Chazelas
9

Se você está interessado em saber o porquê? assim é, você pode ver a resposta nas fontes do kernel aqui :

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

Basicamente, a busca ( *pposnão 0) não é implementada para leituras ( !write) de valores sysctl que são números. Sempre que uma leitura é feita /proc/sys/fs/file-max, a rotina em questão __do_proc_doulongvec_minmax()é chamada a partir da entrada para file-maxna tabela de configuração no mesmo arquivo.

Outras entradas, como as que /proc/sys/kernel/poweroff_cmdsão implementadas proc_dostring(), permitem a busca, para que você possa fazer dd bs=1isso e ler a partir do seu shell sem problemas.

Observe que, desde o kernel 2.6, a maioria das /procleituras foi implementada por meio de uma nova API chamada seq_file e isso suporta buscas, por exemplo, a leitura /proc/statnão deve causar problemas. A /proc/sys/implementação, como podemos ver, não usa essa API.

meuh
fonte
3

Na primeira tentativa, isso parece um bug nas conchas que retornam menos que um Bourne Shell real ou seus derivados derivam (sh, bosh, ksh, herança).

O Bourne Shell original tenta ler um bloco (64 bytes) mais recentes, as variantes do Bourne Shell leem 128 bytes, mas começam a ler novamente se não houver um novo caractere de linha.

Antecedentes: / procfs e implementações similares (por exemplo, o /etc/mtabarquivo virtual montado ) possuem conteúdo dinâmico e uma stat()chamada não causa a recriação do conteúdo dinâmico primeiro. Por esse motivo, o tamanho de um arquivo (da leitura até o EOF) pode diferir do questat() retorna.

Dado que o padrão POSIX exige que os utilitários esperem leituras curtas a qualquer momento, o software que acredita que um read()que retorna menos que a quantidade solicitada de bytes é uma indicação EOF está quebrado. Um utilitário implementado corretamente chama read()uma segunda vez, caso retorne menos que o esperado - até que um 0 seja retornado. No caso do readbuiltin, é claro que seria suficiente ler até EOF ou até que um NLseja visto.

Se você executar trussou um clone de treliça, poderá verificar esse comportamento incorreto para as conchas que retornam apenas 6em seu experimento.

Nesse caso especial, parece ser um bug do kernel do Linux, veja:

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

O kernel do Linux retorna 0 com o segundo reade isso está incorreto.

Conclusão: os shells que primeiro tentam ler uma grande quantidade de dados não acionam esse bug do kernel do Linux.

esperto
fonte
OK, saiu da resposta com uma nova verificação para um bug do kernel do Linux.
schily
Isto não é um erro, é um recurso!
Guntram Blohm apoia Monica
Esta é uma afirmação realmente estranha.
schily
Seria um recurso se estivesse documentado. Lendo kernel.org/doc/Documentation/filesystems/proc.txt , não vejo documentação para o comportamento. Dito isso, está claramente funcionando como planejado pelo implementador; portanto, se isso for considerado um bug, é um bug no design, não na implementação.
Charles Duffy
0

Os arquivos em / proc às vezes usam caracteres NULL para separar os campos dentro do arquivo. Parece que a leitura não é capaz de lidar com isso.

Tony George
fonte