アプリケーションを開発してるとエラー発生時の処理とか面倒なところですが、 特に一部のIO Errorって再現し辛くて、テストがおろそかになってました。
調べてみたら、Linuxのデバイスマッパーを使えば簡単にread/writeのIO Errorを再現できることが分かったので、簡単な手順をまとめました。
# やりたいこと
ファイルのオープン時ではなく、途中までread/writeしてからIO Errorを発生させたい。 (オープン時のエラーは、ファイルの代わりにディレクトリ置いとけば簡単にできるからね)
# 大まかな手順
手順の概要です。
- Readしたいファイルを作っておく
- ループバックデバイスで作ったファイルを/dev/loopXにマップする
- デバイスマッパーで/dev/loopAを/dev/mapper/xxxxにマップする
- なにかプログラムを書いて/dev/mapper/xxxxを読み込む
読み込み前提の順になっていますが、書き込みも同様の手順で可能です。 ですがこの手順だとファイルサイズに上限がありますので、上限エラーも試すことができます。
# 詳細な手順
# Readしたいファイルを作っておく
適当にファイル作りましょう。
$ dd if=/dev/zero of=TEMPFILE bs=512 count=20480
20480+0 records in
20480+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.317796 s, 33.0 MB/s
$ ls -l
total 10240
-rw-r--r-- 1 yogi yogi 10485760 Jul 7 00:52 TEMPFILE
上記は/dev/zeroからとってるので0クリアされた10MBのファイルになります。 デバイスマッパーでのエラーエミュレーションはセクタ単位でしかできないので、512バイト以上にしておいたほうが良いです。 そうじゃないと最初のreadでえらーになっちゃいます。
# ループバックデバイスで/dev/loopXにマップ
$ sudo losetup -f TEMPFILE
$ losetup -l
NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC
/dev/loop0 0 0 0 0 /home/user/TEMPFILE 0 512
/dev/loop?の空いてるデバイスファイルとしてマップされます。('?'の部分は数字になります) 上の例では/dev/loop0に割り当てられたようです。
この状態でも通常ファイルのようにread/writeできますが、デバイスファイルの扱いです、writeしても元ファイルサイズ以上には書き込めません。
# デバイスマッパーで/dev/mapper/xxxxにマップする
まずはマップファイルを用意。 テキストエディタで作ってもいいのですが、下の例ではcatコマンドに標準入力で手入力した例です。 最後まで入力したらCtrl-Dでファイルが出来上がります。
$ cat > MAPFILE
0 10240 linear /dev/loop0 0
10240 1 error
10241 10239 linear /dev/loop0 10241
作ったマップファイルを使って、デバイスマッパーのデバイスを作成します。
$ sudo dmsetup create errdev0 < MAPFILE
$ ls -l /dev/mapper
total 0
crw------- 1 root root 10, 236 Jul 6 14:31 control
lrwxrwxrwx 1 root root 7 Jul 7 01:03 errdev0 -> ../dm-0
errdev0の部分は任意の名前を指定できます。
必要に応じて、シンボリックリンク先(/dev/dm-0)のパーミッションは変更してください。デフォルトではrootしかアクセスできないはずです。
このマップファイルにより、以下のようにセクタが割り当てられます。
| errdev0セクタ | 対象 | 対象セクタ |
|---|---|---|
| 0 - 10239 | /dev/loop0 | 0 - 10239 |
| 10240 | エラー | - |
| 10241 - 20480 | /dev/loop0 | 10241 - 20480 |
つまり、セクタ0からセクタ10239までは/dev/loop0(=MAPFILE)と同じデータ。
セクタ10240にアクセスするとIO Error。
セクタ10241からセクタ20479までは/dev/loop0(=MAPFILE)と同じデータ。
ということになります。
# エラー発生確認
例としてPythonなら以下のようなコードでエラーの発生を確認できます。
import time
with open('/dev/mapper/errdev0', mode="r") as fo:
size = 0
while True:
b = fo.read(10240)
size += len(b)
print(f"{size}")
time.sleep(0.1)
1セクタが512バイトなので(なんでそうなってるのかは未確認ですが)、 10240*512バイト以降をreadしたところでIO Errorとなります。
$ sudo python3 read.py
10240
20480
30720
40960
:
5222400
5232640
5242880
Traceback (most recent call last):
File "read.py", line 6, in <module>
b = fo.read(10240)
OSError: [Errno 5] Input/output error
# 参考
いろいろ参考になりました!