null

Создание проблем при использовании read(2)

В рамках курса "Системное программное обеспечение" в качесте одной из лабораторных работ студентам предлагается ознакомиться с системными вызовами. Одной из характерных проблем в сдаваемом студентами коде является игнорирование ошибок, возвращаемых вызовами. Для таких вызовов как open(2) и write(2) показать возникновение ошибки довольно просто, но вот для того, чтобы read(2) вернул ошибку, надо немного потрудиться.

Время дискет уже давно минуло, держать в системе HDD со сбойными секторами только ради этого затруднительно, а писать библиотеку, которая будет подгружаться через LD_PRELOAD и перехватывать вызов read(2), вроде как не честно.

Однако, используя особенности ZFS, можно относительно просто обеспечить гарантированное возникновение ошибки в нужном месте при чтении файла. По умолчанию для данных ZFS сохраняет на диске контрольную сумму каждого блока. Если на устройстве напрямую изменить один из блоков данных, при вызове read(2) ZFS выявит несовпадение контрольной суммы, и read(2) вернёт ошибку.

Как этого добиться?

Для таких экспериментов лучше создать небольшой отдельный пул, так как прямое исправление данных в "боевом" пуле может закончится неприятностями. Для упрощения работы и в целях экономии дискового пространства создадим для начала файл для пула:

mkfile -n 64m read.pool

Размер 64Мб является минимальным размером пула.

Далее создаём на этом файле пул:

zpool create -O recordsize=4k read $PWD/read.pool

Размер записи уменьшен для того, чтобы блок данных, который нельзя будет прочитать из файла, был минимальным.

Теперь формируем файл, который будет положен на этот пул:

{
  i=0; while [ $i -lt 2048 ]
  do
    printf "%02048s%02047s\n" 0 0
    i=`expr $i + 1`
  done
  printf "%02048s%02047s\n" 0 0 | tr 0 1
  i=0; while [ $i -lt 2048 ]
  do
    printf "%02048s%02047s\n" 0 0
    i=`expr $i + 1`
  done
} > /export/labs/read/file

Как можно заметить, в середину файла вставлена строка из единиц, позицию которой мы и будем искать в пуле:

i=0; while [ $i -lt 16384 ]
do
  dd if=read.pool bs=4096 iseek=$i count=1 2>/dev/null |
    /usr/xpg4/bin/grep -q 11111111 && echo $i
  i=`expr $i + 1`
done

В результате работы этого скрипта должно получиться одно значение - позицию нужного блока в файле. В моём случае получилось число 3083, но оно может оказаться другим. По этой позиции и записываем мусор:

zpool export read
echo XXXXXXXX | dd of=read.pool bs=4096 oseek=3083 conv=notrunc
zpool import -d $PWD read

Готово! Теперь можно проверить, что часть файла не читается:

dd if=file of=/dev/null bs=4096

В результате dd должен завершить свою работу с ошибкой:

read: I/O error
2048+0 records in
2048+0 records out

Что говорит о том, что всё прошло правильно. Также можно проверить код возврата команды dd. Стоит отметить, что некоторые команды Solaris некорректно обрабатывают такую ситуацию:

root@helios:/export/labs/read# cat file > /dev/null
root@helios:/export/labs/read# echo $?
0
root@helios:/export/labs/read# wc -l file 
    2048 file
root@helios:/export/labs/read# echo $?   
0

При этом, например, grep корректно устанавливает код возврата:

root@helios:/export/labs/read# grep -c 1111 file 
0
root@helios:/export/labs/read# echo $?
1

 

Коротко о себе:

Работаю в компании Tune-IT и тьютором кафедры Вычислительной техники в СПбГУИТМО.

Очень люблю команду cat, core solaris и IPv6.