Por que alguns shells 'read' embutidos falham ao ler a linha inteira do arquivo em '/ proc'?

19

Em alguns shells parecidos com Bourne, o read builtin não pode ler a linha inteira do arquivo em /proc (o comando abaixo deve ser executado em zsh , substituir $=shell por $shell por outros 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

read padrão requer que a entrada padrão precisa ser um arquivo de texto , esse requisito causa os comportamentos variados?

Leia a definição POSIX do arquivo de texto , eu confirmo:

$ 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á% de caracteres NUL no conteúdo de /proc/sys/fs/file-max , e também find relatou como um arquivo normal (isso é um bug em find ?).

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

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty
    
por cuonglm 12.05.2016 / 10:22

4 respostas

30

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

Como são dados dinâmicos, você só pode fazer uma chamada read() do sistema neles (pelo menos para alguns deles). Fazendo mais do que um poderia obter dois pedaços de dois conteúdos diferentes, então, em vez disso, parece um segundo read() neles apenas não retorna nada (ou seja, fim de arquivo) (a menos que vocêlseek() volta para o início (e para o começando apenas)).

O utilitário read precisa ler o conteúdo dos arquivos um byte de cada vez para ter certeza de não ler além do caractere de nova linha. Isso é o que o dash faz:

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

Alguns shells como bash têm uma otimização para evitar a necessidade de fazer tantas chamadas de sistema read() . Primeiro, eles verificam se o arquivo é pesquisável e, em caso afirmativo, leem em blocos e, em seguida, eles sabem que podem colocar o cursor de volta logo após a nova linha, se tiverem lido o texto:

$ 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 com arquivos proc maiores do que 128 bytes e que só podem ser lidos em uma chamada de sistema lida.

bash também parece desativar essa otimização quando a opção -d é usada.

ksh93 leva a otimização ainda mais a ponto de se tornar falsa. O read de ksh93 procura novamente, mas lembra-se dos dados extras que leu para o próximo read , então o próximo read (ou qualquer um de seus outros integrantes que leiam dados como cat ou head ) não faz • nem tente read dos dados (mesmo que esses dados tenham sido modificados por outros comandos intermediários):

$ 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
    
por 12.05.2016 / 11:01
8

Se você estiver interessado em saber por quê? , é possível ver a responda nas fontes do kernel aqui :

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

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

Outras entradas, como /proc/sys/kernel/poweroff_cmd , são implementadas por meio de proc_dostring() , que permite buscas, para que você possa fazer dd bs=1 sobre ele e ler a partir do seu shell sem problemas.

Observe que, desde o kernel 2.6, a maioria das /proc reads foram implementadas por meio de uma nova API chamado seq_file e isso suporta procura por isso, por exemplo, lendo /proc/stat não deve causar problemas. A implementação de /proc/sys/ , como podemos ver, não usa isso api.

    
por 12.05.2016 / 17:41
2

Na primeira tentativa, isso parece um bug nos shells que retornam menos do que uma Bourne Shell real ou seus derivados retornam (sh, bosh, ksh, heirloom).

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

Histórico: / procfs e implementações semelhantes (por exemplo, o arquivo /etc/mtab virtual montado) têm conteúdo dinâmico e uma chamada stat() não causa a recriação do conteúdo dinâmico primeiro. Por esse motivo, o tamanho de tal arquivo (da leitura até o EOF) pode diferir do que o stat() retorna.

Dado que o padrão POSIX requer que os utilitários esperem leituras curtas a qualquer momento, o software que acredita que um read() que retorna menos que a quantidade de bytes solicitados é uma indicação de EOF está quebrada. 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 read builtin, é claro que seria suficiente ler até EOF ou até que NL seja visto.

Se você executar truss ou um clone de treliça, será possível verificar o comportamento incorreto dos shells que retornam apenas 6 em sua experiência.

Neste caso especial, parece ser um erro 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 read e isso é obviamente incorreto.

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

    
por 12.05.2016 / 11:22
0

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

    
por 13.05.2016 / 08:18