USB接続された外部HDDのシリアルナンバーとWWNが同一になる問題

いくつかの外付けUSB HDD用制御チップは、UASPモードの場合にディスクのシリアルナンバーとWWNを正しく報告しない。これにより/dev/disk/by-id/を用いたプログラムが想定通りに動作しないことがある。Linuxの場合、uasドライバーの代わりにusb-storageドライバーを使用することで解決できる。

Sabrentというメーカーが販売している5ベイの3.5インチHDDエンクロージャー(DS-SC5B)を買った。このエンクロージャーはRAID機能は搭載せず、単に最大5つのHDDをUSB 3.2 Gen 2 (10 Gbps)で接続できるだけのシンプルなものになっている。搭載されている制御チップは、ASMedia TechnologyのASM235CMのようだ。

このエンクロージャーをPCに接続して、Ubuntu 22.04をインストールしようとしたところ、インストーラーがディスクのフォーマットの段階でクラッシュする現象が発生した。Ubuntuの場合、本来はUSB接続のHDDに問題なくインストールが可能で、そこから問題なくブートすることができる。

インストール時のディスクの設定はmdadmでRAID1を組み、その上にLVMでVGとLVを作るというよくある構成であったが、色々と試してみたところ、エンクロージャーに内蔵されているディスクを2台以上設定しようとするとクラッシュすることがわかった。

クラッシュ時のエラーメッセージは以下のようなものになる。

start: cmd-install/stage-partitioning/builtin/cmd-block-meta: configuring partition: partition-1
get_path_to_storage_volume for volume disk-sdc({'ptable': 'gpt', 'serial': '35000000000000001', 'wwn': '0x5000000000000001', 'path': '/dev/sdc', 'preserve': False, 'name': '', 'grub_device': False, 'type': 'disk', 'id': 'disk-sdc'})
Processing serial 0x5000000000000001 via udev to 0x5000000000000001
lookup_disks found: ['wwn-0x5000000000000001', 'wwn-0x5000000000000001-part1']
Running command ['udevadm', 'info', '--query=property', '--export', '/dev/sdb'] with allowed return codes [0] (capture=True)
/dev/sdb is multipath device? False
Running command ['udevadm', 'info', '--query=property', '--export', '/dev/sdb'] with allowed return codes [0] (capture=True)
/dev/sdb is multipath device member? False
block.lookup_disk() returning path /dev/sdb
Running command ['partprobe', '/dev/sdb'] with allowed return codes [0, 1] (capture=False)
Running command ['udevadm', 'settle'] with allowed return codes [0] (capture=False)
TIMED udevadm_settle(): 0.181
devsync happy - path /dev/sdb now exists
return volume path /dev/sdb
Running command ['lsblk', '--noheadings', '--bytes', '--pairs', '--output=ALIGNMENT,DISC-ALN,DISC-GRAN,DISC-MAX,DISC-ZERO,FSTYPE,GROUP,KNAME,LABEL,LOG-SEC,MAJ:MIN,MIN-IO,MODE,MODEL,MOUNTPOINT,NAME,OPT-IO,OWNER,PHY-SEC,RM,RO,ROTA,RQ-SIZE,SIZE,STATE,TYPE,UUID', '/dev/sdb'] with allowed return codes [0] (capture=True)
get_blockdev_sector_size: info:
{
 "sdb": {
  "ALIGNMENT": "0",
  "DISC-ALN": "0",
  "DISC-GRAN": "0",
  "DISC-MAX": "0",
  "DISC-ZERO": "0",
  "FSTYPE": "",
  "GROUP": "disk",
  "KNAME": "sdb",
  "LABEL": "",
  "LOG-SEC": "512",
  "MAJ:MIN": "8:16",
  "MIN-IO": "4096",
  "MODE": "brw-rw----",
  "MODEL": "ASM235CM",
  "MOUNTPOINT": "",
  "NAME": "sdb",
  "OPT-IO": "0",
  "OWNER": "root",
  "PHY-SEC": "4096",
  "RM": "0",
  "RO": "0",
  "ROTA": "1",
  "RQ-SIZE": "60",
  "SIZE": "4000787030016",
  "STATE": "running",
  "TYPE": "disk",
  "UUID": "",
  "device_path": "/dev/sdb"
 },
 "sdb1": {
  "ALIGNMENT": "0",
  "DISC-ALN": "0",
  "DISC-GRAN": "0",
  "DISC-MAX": "0",
  "DISC-ZERO": "0",
  "FSTYPE": "vfat",
  "GROUP": "disk",
  "KNAME": "sdb1",
  "LABEL": "",
  "LOG-SEC": "512",
  "MAJ:MIN": "8:17",
  "MIN-IO": "4096",
  "MODE": "brw-rw----",
  "MODEL": "",
  "MOUNTPOINT": "",
  "NAME": "sdb1",
  "OPT-IO": "0",
  "OWNER": "root",
  "PHY-SEC": "4096",
  "RM": "0",
  "RO": "0",
  "ROTA": "1",
  "RQ-SIZE": "60",
  "SIZE": "1127219200",
  "STATE": "",
  "TYPE": "part",
  "UUID": "E307-5D1B",
  "device_path": "/dev/sdb1"
 }
}
get_blockdev_sector_size: (log=512, phys=4096)
sdb logical_block_size_bytes: 512
adding partition 'partition-1' to disk 'disk-sdc' (ptable: 'gpt')
partnum: 1 offset_sectors: 2048 length_sectors: 2201599
Preparing partition location on disk /dev/sdb
Wiping 1M on /dev/sdb at offset 1048576
Running command ['sgdisk', '--new', '1:2048:2203647', '--typecode=1:ef00', '/dev/sdb'] with allowed return codes [0] (capture=True)
An error occured handling 'partition-1': ProcessExecutionError - Unexpected error while running command.
Command: ['sgdisk', '--new', '1:2048:2203647', '--typecode=1:ef00', '/dev/sdb']
Exit code: 4
Reason: -
Stdout: ''
Stderr: Could not create partition 1 from 2048 to 2203647
        Error encountered; not saving changes.
        
finish: cmd-install/stage-partitioning/builtin/cmd-block-meta: FAIL: configuring partition: partition-1
TIMED BLOCK_META: 5.419
finish: cmd-install/stage-partitioning/builtin/cmd-block-meta: FAIL: curtin command block-meta
Traceback (most recent call last):
  File "/snap/subiquity/3359/lib/python3.8/site-packages/curtin/commands/main.py", line 202, in main
    ret = args.func(args)
  File "/snap/subiquity/3359/lib/python3.8/site-packages/curtin/log.py", line 97, in wrapper
    return log_time("TIMED %s: " % msg, func, *args, **kwargs)
  File "/snap/subiquity/3359/lib/python3.8/site-packages/curtin/log.py", line 79, in log_time
    return func(*args, **kwargs)
  File "/snap/subiquity/3359/lib/python3.8/site-packages/curtin/commands/block_meta.py", line 117, in block_meta
    return meta_custom(args)
  File "/snap/subiquity/3359/lib/python3.8/site-packages/curtin/commands/block_meta.py", line 2045, in meta_custom
    handler(command, storage_config_dict, command_handlers)
  File "/snap/subiquity/3359/lib/python3.8/site-packages/curtin/commands/block_meta.py", line 998, in partition_handler
    util.subp(cmd, capture=True)
  File "/snap/subiquity/3359/lib/python3.8/site-packages/curtin/util.py", line 275, in subp
    return _subp(*args, **kwargs)
  File "/snap/subiquity/3359/lib/python3.8/site-packages/curtin/util.py", line 139, in _subp
    raise ProcessExecutionError(stdout=out, stderr=err,
curtin.util.ProcessExecutionError: Unexpected error while running command.
Command: ['sgdisk', '--new', '1:2048:2203647', '--typecode=1:ef00', '/dev/sdb']
Exit code: 4
Reason: -
Stdout: ''
Stderr: Could not create partition 1 from 2048 to 2203647
        Error encountered; not saving changes.
        
Unexpected error while running command.
Command: ['sgdisk', '--new', '1:2048:2203647', '--typecode=1:ef00', '/dev/sdb']
Exit code: 4
Reason: -
Stdout: ''
Stderr: Could not create partition 1 from 2048 to 2203647
        Error encountered; not saving changes.
        
curtin: Installation failed with exception: Unexpected error while running command.
Command: ['curtin', 'block-meta', 'simple']
Exit code: 3

エラーをよく見ると、get_path_to_storage_volume()という関数を使って/dev/sdcを探しているにもかかわらず、途中から処理が/dev/sdbに置き換わってしまっている。そのため、本来/dev/sdcに作るはずだったパーティションを/dev/sdbに作ろうとしてしまい、エラーになっている。

get_path_to_storage_volume()の実装を見てみると、与えられた引数を使ってデバイスの特定を試みるにあたって、wwnserialdisk_idpathの順番に値を使用して検索している。wwnserialに関しては、単純に/dev/disk/by-idの中に値が含まれるファイルが存在しているかを見ているようだ。

ここで、シリアルナンバーとして与えられている35000000000000001とWWNの0x5000000000000001が問題となる。一見して怪しいこれらの番号だが、実際のところ、接続されたHDDに関わらず一定の値が報告されているようだ。

実際に、udevadmを用いて各HDDのシリアルナンバーとWWNを確認すると、どのデバイスも同じ値が報告されている。

$ sudo udevadm info --query=all --name=/dev/sdb
P: /devices/pci0000:00/0000:00:14.0/usb2/2-5/2-5.3/2-5.3.1/2-5.3.1:1.0/host2/target2:0:0/2:0:0:0/block/sdb
N: sdb
L: 0
S: disk/by-id/scsi-35000000000000001
S: disk/by-path/pci-0000:00:14.0-usb-0:5.3.1:1.0-scsi-0:0:0:0
S: disk/by-id/wwn-0x5000000000000001
S: disk/by-id/scsi-SASMT_ASM235CM_10C000000519
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb2/2-5/2-5.3/2-5.3.1/2-5.3.1:1.0/host2/target2:0:0/2:0:0:0/block/sdb
E: DEVNAME=/dev/sdb
E: DEVTYPE=disk
E: DISKSEQ=10
E: MAJOR=8
E: MINOR=16
E: SUBSYSTEM=block
E: USEC_INITIALIZED=4864398
E: SCSI_TPGS=0
E: SCSI_TYPE=disk
E: SCSI_VENDOR=ASMT
E: SCSI_VENDOR_ENC=ASMT\x20\x20\x20\x20
E: SCSI_MODEL=ASM235CM
E: SCSI_MODEL_ENC=ASM235CM\x20\x20\x20\x20\x20\x20\x20\x20
E: SCSI_REVISION=0
E: ID_SCSI=1
E: ID_SCSI_INQUIRY=1
E: SCSI_IDENT_SERIAL=10C000000519
E: SCSI_IDENT_LUN_NAA_REG=5000000000000001
E: ID_VENDOR=ASMT
E: ID_VENDOR_ENC=ASMT\x20\x20\x20\x20
E: ID_MODEL=ASM235CM
E: ID_MODEL_ENC=ASM235CM\x20\x20\x20\x20\x20\x20\x20\x20
E: ID_REVISION=0
E: ID_TYPE=disk
E: ID_WWN_WITH_EXTENSION=0x5000000000000001
E: ID_WWN=0x5000000000000001
E: ID_BUS=scsi
E: ID_SERIAL=35000000000000001
E: ID_SERIAL_SHORT=5000000000000001
E: ID_SCSI_SERIAL=10C000000519
E: MPATH_SBIN_PATH=/sbin
E: DM_MULTIPATH_DEVICE_PATH=0
E: ID_PATH=pci-0000:00:14.0-usb-0:5.3.1:1.0-scsi-0:0:0:0
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_5_3_1_1_0-scsi-0_0_0_0
E: ID_PART_TABLE_UUID=5dde5dc1-6c68-40cb-b9f1-3683ea376140
E: ID_PART_TABLE_TYPE=gpt
E: DEVLINKS=/dev/disk/by-id/scsi-35000000000000001 /dev/disk/by-path/pci-0000:00:14.0-usb-0:5.3.1:1.0-scsi-0:0:0:0 /dev/disk/by-id/wwn-0x5000000000000001 /dev/disk/by-id/scsi-SASMT_ASM235CM_10C000000519
E: TAGS=:systemd:
E: CURRENT_TAGS=:systemd:

$ sudo udevadm info --query=all --name=/dev/sdc
P: /devices/pci0000:00/0000:00:14.0/usb2/2-5/2-5.3/2-5.3.3/2-5.3.3:1.0/host3/target3:0:0/3:0:0:0/block/sdc
N: sdc
L: 0
S: disk/by-id/wwn-0x5000000000000001
S: disk/by-id/scsi-SASMT_ASM235CM_30C000000519
S: disk/by-id/scsi-35000000000000001
S: disk/by-path/pci-0000:00:14.0-usb-0:5.3.3:1.0-scsi-0:0:0:0
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb2/2-5/2-5.3/2-5.3.3/2-5.3.3:1.0/host3/target3:0:0/3:0:0:0/block/sdc
E: DEVNAME=/dev/sdc
E: DEVTYPE=disk
E: DISKSEQ=11
E: MAJOR=8
E: MINOR=32
E: SUBSYSTEM=block
E: USEC_INITIALIZED=5264814
E: SCSI_TPGS=0
E: SCSI_TYPE=disk
E: SCSI_VENDOR=ASMT
E: SCSI_VENDOR_ENC=ASMT\x20\x20\x20\x20
E: SCSI_MODEL=ASM235CM
E: SCSI_MODEL_ENC=ASM235CM\x20\x20\x20\x20\x20\x20\x20\x20
E: SCSI_REVISION=0
E: ID_SCSI=1
E: ID_SCSI_INQUIRY=1
E: SCSI_IDENT_SERIAL=30C000000519
E: SCSI_IDENT_LUN_NAA_REG=5000000000000001
E: ID_VENDOR=ASMT
E: ID_VENDOR_ENC=ASMT\x20\x20\x20\x20
E: ID_MODEL=ASM235CM
E: ID_MODEL_ENC=ASM235CM\x20\x20\x20\x20\x20\x20\x20\x20
E: ID_REVISION=0
E: ID_TYPE=disk
E: ID_WWN_WITH_EXTENSION=0x5000000000000001
E: ID_WWN=0x5000000000000001
E: ID_BUS=scsi
E: ID_SERIAL=35000000000000001
E: ID_SERIAL_SHORT=5000000000000001
E: ID_SCSI_SERIAL=30C000000519
E: MPATH_SBIN_PATH=/sbin
E: DM_MULTIPATH_DEVICE_PATH=0
E: ID_PATH=pci-0000:00:14.0-usb-0:5.3.3:1.0-scsi-0:0:0:0
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_5_3_3_1_0-scsi-0_0_0_0
E: DEVLINKS=/dev/disk/by-id/wwn-0x5000000000000001 /dev/disk/by-id/scsi-SASMT_ASM235CM_30C000000519 /dev/disk/by-id/scsi-35000000000000001 /dev/disk/by-path/pci-0000:00:14.0-usb-0:5.3.3:1.0-scsi-0:0:0:0
E: TAGS=:systemd:
E: CURRENT_TAGS=:systemd:

シリアルナンバーとWWNが同じということは、/dev/disk/by-id/wwn-0x5000000000000001/dev/disk/by-id/scsi-35000000000000001の指す先が一意に定まらなくなる。これにより、Ubuntuのインストーラーのように、/dev/disk/by-id/以下に依存したロジックを含むアプリケーションが異常な動作をするようになる。

このような振る舞いをするUSBの外付けHDDはいくつか存在するらしく、インターネット上で複数の報告を見ることができる。そのうちの一つから以下のような情報が得られた(Seagateからの情報らしい)。

The issue seems to be related to BOT vs UAS. On new machines with UAS you get the same Unique ID ...(中略)... The issue is not seen when connected to a host via BOT and is only seen when connected via UAS.

このような現象はUSB Attached SCSI (UAS)では発生するが、Bulk Only Transport(BOT)では起こらないらしい。UASはUSB 3.0から追加された規格で、従来から存在するBOTに比べると新しい規格となる(とはいえ、USB 3.0自体がすでに十分古い規格ではあるが)。

Ubuntu 22.04の場合、USBマスストレージをUASモードで操作するためのuasカーネルモジュールは、デフォルトでインストールされている。そのためUASP対応のデバイスを接続すると自動でuasドライバーが使用される。UASP非対応のデバイスの場合、従来のBOT実装であるusb-stoageドライバーが使われる。

今回のエンクロージャーの場合も、強制的にusb-storageドライバーを割り当てると、ディスクの名前も含めてすべての情報を正常に得ることが出来た。制御チップの問題なのか、UASモードでは情報が正しく報告されず、BOTモードでのみ期待通りの動作をするようだ。

現時点でユーザー側ができる対応策としては、2つの方法が考えられる。usb-storageドライバーを強制的に使用する方法と、udevルールを編集して/dev/disk/by-id以下のエントリーを作らなくする方法である。

usb-storageドライバーを強制的に使用したい場合、以下の方法でmodprobeを設定する。

最初に、エンクロージャーのデバイスIDを取得する。lsusbコマンドを使用すると簡単に見つけることができる。

$ lsusb
Bus 002 Device 005: ID 174c:55aa ASMedia Technology Inc. ASM1051E SATA 6Gb/s bridge, ASM1053E SATA 6Gb/s bridge, ASM1153 SATA 3Gb/s bridge, ASM1153E SATA 6Gb/s bridge
Bus 002 Device 004: ID 174c:55aa ASMedia Technology Inc. ASM1051E SATA 6Gb/s bridge, ASM1053E SATA 6Gb/s bridge, ASM1153 SATA 3Gb/s bridge, ASM1153E SATA 6Gb/s bridge
...

上記の例ではIDの右側の174c:55aaがデバイスのIDとなる。

次に、/etc/modprobe.d/内に適当な設定ファイルを作って、quirksに上記のIDを指定してusb-storageを使用するように設定する。quirksに与えるIDには:uを追加しておく。

$ cat /etc/modprobe.d/smc235c.conf
options usb-storage quirks=174c:55aa:u

ファイルを作ったらinitramfsを更新してから再起動する。

$ sudo update-initramfs -u
$ sudo reboot

なお、grubのカーネルパラメーターにusb-storage.quirks=174c:55aa:uと追加する方法でも同様の効果があるらしい(私自身は試していない)。

usb-storageドライバーを使用する場合、性能面では劣化が発生する可能性がある。とはいえ、渡しの場合は使用しているHDDが5400rpmで、しかも1Gbpsで接続されたNASとして使用するので、性能面ではあまり気にならなかった。

udevのルールを編集する場合、Ubuntuでは/lib/udev/rules.d/60-persistent-storage.rules内のルールを変更する必要がある。/etc/udev/rules.d/内に同名のフィルを作った上で、disk/by-id関連のルールにENV{SCSI_MODEL}!="ASM235CM"を追加する。

なお、Ubuntuのインストール中に応急処置を行うのであれば、F2でシェルに入って、/dev/disk/by-id/内のファイルを消してしまうのが早い。