¿Por qué algunos shells `read` incorporados no pueden leer la línea completa del archivo en` / proc`?

19

En algunos Bourne como conchas, la readorden interna no puede leer toda la línea del archivo en /proc(el siguiente comando debe ser ejecutado en zsh, sustituir $=shellcon $shellotras 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

readEl estándar requiere que la entrada estándar deba ser un archivo de texto , ¿ese requisito causa comportamientos variados?


Lea la definición POSIX del archivo de texto , hago algunas verificaciones:

$ 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

No hay NULcarácter en el contenido de /proc/sys/fs/file-max, y también lo findinformó como un archivo normal (¿Es esto un error find?).

Supongo que el caparazón hizo algo debajo del capó, como file:

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

Respuestas:

31

El problema es que esos /procarchivos en Linux aparecen como archivos de texto en lo que stat()/fstat()respecta, pero no se comportan como tales.

Debido a que son datos dinámicos, solo puede hacer una read()llamada al sistema (al menos para algunos de ellos). Hacer más de uno podría obtener dos fragmentos de dos contenidos diferentes, por lo que parece que un segundo read()en ellos simplemente no devuelve nada (es decir, el final del archivo) (a menos que lseek()regrese al principio (y solo al principio)).

La readutilidad necesita leer el contenido de los archivos de un byte a la vez para asegurarse de no leer más allá del carácter de nueva línea. Eso es lo que dashhace:

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

Algunos shells como bashtienen una optimización para evitar tener que hacer tantas read()llamadas al sistema. Primero comprueban si el archivo es buscable, y si es así, leen en fragmentos ya que saben que pueden volver a colocar el cursor justo después de la nueva línea si han leído más allá:

$ 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, aún tendría problemas para los archivos de proceso que tienen más de 128 bytes y solo se pueden leer en una llamada al sistema de lectura.

bashTambién parece deshabilitar esa optimización cuando -dse utiliza la opción.

ksh93lleva la optimización aún más lejos como para volverse falso. ksh93 readbusca, pero recuerda los datos adicionales que ha leído para el siguiente read, por lo que el siguiente read(o cualquiera de sus otros componentes integrados que leen datos como cato head) ni siquiera trata readlos datos (incluso si esos datos han sido modificados por otros comandos en el medio):

$ 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
fuente
¡Ah, sí, una straceexplicación basada en datos es mucho más fácil de entender!
Stephen Kitt
Gracias, los datos dinámicos tienen sentido. Entonces, ¿cómo detecta el shell sus datos dinámicos? Si lo hago cat /proc/sys/fs/file-max | ..., el problema se ha ido.
Cuonglm
3
El caparazón no lo detecta. El hecho de que sean datos dinámicos significa que procfsno puede manejar múltiples read(2)llamadas sucesivas al mismo archivo; El comportamiento no depende del shell. El uso caty la tubería funciona porque catlee el archivo en fragmentos lo suficientemente grandes; el shell readincorporado luego lee de la tubería un carácter a la vez.
Stephen Kitt
1
Hay una solución un poco sucia en mksh. read -N 10 a < /proc/sys/fs/file-max
Ipor Sircer
1
@IporSircer. En efecto. Una solución similar parece funcionar con zsh: read -u0 -k10(o uso sysread; $mapfile[/proc/sys/fs/file-max]no funciona ya que esos archivos no se pueden mmapeditar). En cualquier caso, con cualquier shell, siempre se puede a=$(cat /proc/sys/fs/file-max). Algunos incluyen mksh, zshy ksh93, a=$(</proc/sys/fs/file-max)también funciona y no bifurca un proceso para hacer la lectura.
Stéphane Chazelas
9

Si estás interesado en saber por qué? Esto es así, puede ver la respuesta en las fuentes del núcleo aquí :

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

Básicamente, la búsqueda ( *pposno 0) no se implementa para lecturas ( !write) de valores sysctl que son números. Cada vez que se realiza una lectura /proc/sys/fs/file-max, la rutina en cuestión __do_proc_doulongvec_minmax()se llama desde la entrada de file-maxla tabla de configuración en el mismo archivo.

Otras entradas, como las que /proc/sys/kernel/poweroff_cmdse implementan a través de las proc_dostring()cuales permiten búsquedas, para que pueda hacerlo dd bs=1y leer desde su shell sin problemas.

Tenga en cuenta que desde el kernel 2.6 la mayoría de las /proclecturas se implementaron a través de una nueva API llamada seq_file y esto admite búsquedas, por ejemplo, la lectura /proc/statno debería causar problemas. La /proc/sys/implementación, como podemos ver, no utiliza esta API.

meuh
fuente
3

En el primer intento, esto parece un error en los shells que devuelven menos que un Bourne Shell real o sus derivados (sh, bosh, ksh, reliquia).

El Bourne Shell original intenta leer un bloque (64 bytes). Las variantes más recientes del Bourne Shell leen 128 bytes, pero comienzan a leer nuevamente si no hay un nuevo carácter de línea.

Antecedentes: / procfs e implementaciones similares (por ejemplo, el /etc/mtabarchivo virtual montado ) tienen contenido dinámico y una stat()llamada no causa la recreación del contenido dinámico primero. Por esta razón, el tamaño de dicho archivo (desde la lectura hasta el EOF) puede diferir de lo que stat()devuelve.

Dado que el estándar POSIX requiere que las empresas de servicios públicos esperen lecturas cortas en cualquier momento, el software que cree que un valor read()que devuelve menos que la cantidad ordenada de bytes es una indicación EOF está roto. Una utilidad implementada correctamente llama read()por segunda vez en caso de que devuelva menos de lo esperado, hasta que se devuelva un 0. En el caso de la readconstrucción, por supuesto, sería suficiente leer hasta EOF o hasta que NLse vea un.

Si corres trusso haces un clon de truss, deberías poder verificar ese comportamiento incorrecto para los shells que solo regresan 6en tu experimento.

En este caso especial, parece ser un error del kernel de Linux, vea:

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

El kernel de Linux devuelve 0 con el segundo ready, por supuesto, esto es incorrecto.

Conclusión: los shells que primero intentan leer una porción de datos lo suficientemente grande no activan este error del kernel de Linux.

astuto
fuente
OK, salí de la respuesta con una nueva verificación para un error del kernel de Linux.
schily
¡No es un error, es una característica!
Guntram Blohm apoya a Monica
Este es un reclamo realmente extraño.
schily
Sería una característica si se documentara. Leyendo kernel.org/doc/Documentation/filesystems/proc.txt , no veo documentación para el comportamiento. Dicho esto, claramente está funcionando según lo previsto por el implementador, por lo que si esto se considera un error, es un error en el diseño, no en la implementación.
Charles Duffy
0

Los archivos bajo / proc a veces usan caracteres NULL para separar los campos dentro del archivo. Parece que leer no puede manejar esto.

Tony George
fuente