Archivo especial que causa error de E / S

13

Quiero probar automáticamente si una pieza de software reacciona como se esperaba si un archivo esencial de SQLite DB no se puede leer (causando un error de E / S). Exactamente eso sucedió hace unos días en un cliente. Lo arreglamos manualmente, pero ahora quiero crear un código automático para arreglarlo y necesito acceso a un archivo roto para probarlo.

Como todo en Unix es un archivo, sospeché que podría haber un archivo especial que siempre causa errores de E / S cuando uno intenta leerlo (por ejemplo, en / dev).

Algunos archivos similares (imo) serían:

  • /dev/full que siempre dice "No queda espacio en el dispositivo" si intentas escribirlo
  • /dev/null y /dev/zero

así que supuse que solo tenía que haber un archivo como ese (pero aún no he encontrado uno).

¿Alguien conoce ese archivo o cualquier otro método para obtener el resultado deseado (una imagen de partición intencionalmente defectuosa, un contenedor alrededor de open () usando LD_PRELOAD, ...)?
¿Cuál es la mejor manera de ir aquí?

mreithub
fuente
Hasta donde yo sé, no hay un archivo especial en Linux que le dé a SIGIO cuando lees de él. La última vez que obtuve un SIGIO fue por una memoria USB que declaró una capacidad mucho más grande que la real, física. Tal vez eso podría ser una posibilidad?
lgeorget
hmmm, podría intentarlo con una pequeña imagen de partición que recortaré en el medio ...
mreithub
SIGIO no significa que haya habido un error, es una forma en que un programa puede solicitar que se le notifique que ahora no es posible bloquear IO, en lugar de llamar a select () o poll ().
psusi
Ups, sí, tienes razón, por supuesto. Escribí SIGIO pero estaba pensando en el código de error EIO. Pero tal vez el OP también? ¿Por qué una falta de lectura daría un SIGIO?
lgeorget
oh, cometí el mismo error en la pregunta ... lo
edité

Respuestas:

8

Puede usar dmsetuppara crear un dispositivo de mapeador de dispositivos usando los objetivos erroro flakeypara simular fallas.

dmsetup create test --table '0 123 flakey 1 0 /dev/loop0'

Donde 123 es la longitud del dispositivo, en sectores y / dev / loop0 es el dispositivo original en el que desea simular errores. Por error, no necesita los argumentos posteriores ya que siempre devuelve un error.

psusi
fuente
1
Encuentro al menos dos errores en ese comando: el nombre del dispositivo que falta, el error tipográfico y ¿qué se supone que significa "1 0 / dev / null"?
Hauke ​​Laging
@HaukeLaging, ahh, sí, omití el nombre y de alguna manera acerté con la cita incorrecta. El 1 0 / dev / null significa 1 objetivo, comenzando en el desplazamiento 0, respaldado por dispositivo / dev / null. Es necesario para flakey, pero aparentemente es opcional por error.
psusi
Me parece que no es "opcional" sino simplemente ignorado. Puedes consultar con dmsetup table test. Incluso puedes escribir foo bardetrás error; simplemente no le importa (y, por lo tanto, debería eliminarse).
Hauke ​​Laging
@HaukeLaging, editado.
psusi
Gracias por la respuesta, creo que así es como voy a ir por ahora. El único problema menor que tengo con esto es que requiere acceso a la raíz, pero supongo que lo necesitarás de todos modos o cosas de bajo nivel ... (Cuando tenga tiempo, profundizaré en la idea LD_PRELOAD).
mreithub
14

Ya hay un gran conjunto de respuestas sobre Stack Overflow y Server Fault, pero faltaban algunas técnicas. Para hacer la vida más fácil, aquí hay una lista de mecanismos de inyección de fallas de E / S de dispositivos de bloques VM / Linux / sistema de archivos Linux / biblioteca de espacio de usuario de Linux:

Dato adicional : SQLite tiene un controlador VFS para simular errores para que pueda obtener una buena cobertura de prueba.

Relacionado:

Luego
fuente
5

Desea un mecanismo de inyección de fallas para E / S.

En Linux, aquí hay un método que no requiere ninguna configuración previa y genera un error inusual (no EIO "Error de entrada / salida" sino ESRCH "No existe tal proceso"):

cat /proc/1234/mem

donde 1234 es el PID de un proceso que se ejecuta como el mismo usuario que el proceso que está probando, pero no ese proceso en sí. Créditos a rubasov para pensar de /proc/$pid/mem.

Si usa el PID del proceso en sí, obtendrá EIO, pero solo si está leyendo desde un área que no está asignada en la memoria del proceso. La primera página nunca se asigna, por lo que está bien si lee el archivo secuencialmente, pero no es adecuado para un proceso de base de datos que busca directamente en el medio del archivo.

Con algo más de configuración como root, puede aprovechar el mapeador de dispositivos para crear archivos con sectores válidos y sectores defectuosos.

Otro enfoque sería implementar un pequeño sistema de archivos FUSE . EIO es el código de error predeterminado cuando el controlador del sistema de archivos del espacio de usuario hace algo mal, por lo que es fácil de lograr. Tanto los enlaces de Perl como los de Python vienen con ejemplos para comenzar, puede escribir rápidamente un sistema de archivos que refleje principalmente los archivos existentes pero inyecta un EIO en lugares cuidadosamente seleccionados. Existe un sistema de archivos de este tipo: petardfs ( artículo ), no sé qué tan bien funciona fuera de la caja.

Otro método más es un LD_PRELOADenvoltorio. Uno existente es Libfiu (inyección de fallas en el espacio de usuario). Funciona precargando una biblioteca que sobrecarga las llamadas a la API POSIX. Puede escribir directivas simples o código C arbitrario para anular el comportamiento normal.

Gilles 'SO- deja de ser malvado'
fuente
Libfiu parece realmente prometedor (y está en los repositorios de Debian). Gran respuesta, gracias, +1
mreithub
1

La solución es mucho más fácil si está bien usar un archivo de dispositivo como "archivo con errores de E / S". Mi propuesta es para aquellos casos en los que un archivo normal tendrá tales errores.

> dd if=/dev/zero of=/path/to/ext2.img bs=10M count=10
> losetup /dev/loop0 /path/to/ext2.img
> blockdev --getsz /dev/loop0
204800
> echo "0 204800 linear /dev/loop0 0" | dmsetup create sane_dev
> mke2fs /dev/mapper/sane_dev # ext2 reicht
> mount -t ext2 /dev/mapper/sane_dev /some/where
> dd if=/dev/zero of=/some/where/unreadable_file bs=512 count=4
> hdparm --fibmap /some/where/unreadable_file
/mnt/tmp/unreadable_file:
 filesystem blocksize 1024, begins at LBA 0; assuming 512 byte sectors.
 byte_offset  begin_LBA    end_LBA    sectors
           0       2050       2053          4
> umount /dev/mapper/sane_dev
> dmsetup remove sane_dev
> start_sector=$((204800-2053-1))
> echo $'0 2053 linear /dev/loop0 0\n2053 1 error\n2054 '"${start_sector} linear /dev/loop0 2054" | 
>   dmsetup create error_dev
> mount -t ext2 /dev/mapper/error_dev /some/where
> cat /some/where/unreadable_file # 3rd sector of file is unreadable
cat: /some/where/unreadable_file: Input/output error

Debo admitir que estoy un poco confundido porque no he logrado leer sectores individuales de ese archivo sin un error (con dd .. seek=...). Quizás sea un problema de lectura anticipada.

Hauke ​​Laging
fuente
Los bloques de su sistema de archivos tienen un tamaño de al menos 4096 bytes, por lo que abarcarán múltiples sectores incluso si el archivo es pequeño.
Anon