mount_msdosfs の文字コード

21世紀になってから20年近くも経つと言うのに、私たちは未だに 文字化けに取り憑かれている。滑稽である。

MS-DOS時代(だいたい1990年代後半)のMOディスクの中身を取り出そうと思い、 FreeBSD機に繋げて mount_msdosfs を使用したところ、ファイル名に関連して エラーが出た。カーネルハックして原因が判ったので、対策を記録しておく。

OS のバージョンは FreeBSD 11.2R 。

直面したエラーには 2種類あった。 チルダ記号(0x7e)に関するものと、「最」の字に関するもの。

チルダ記号(0x7e)のエラー

mount_msdosfs(8) は -D と -L オプションで文字コードを指定するようになっている。 -D はマウントするファイルシステムの文字コードの指定で、 今回は日本語MS-DOSで使用していたMOディスクなので SJIS になる。 -L の方はOS側の文字コードで、-L ja_JP.UTF-8 のようにロケール名を与える。 -D と -L を指定することで、カーネル内部で自動的に文字コードの変換が行われ、 ファイルシステムとOSの文字コードの違いが吸収される仕組みになっている。

さて、ネットを漁ると、mount_msdosfs には -L ja_JP.eucJP を与える方が良い、 と書かれたブログ記事が多いようだ。10年くらい前の記事で、かつて FreeBSD カーネルのマルチバイト化が不十分だった頃の対処法らしい。 しかし現在では UTF-8 でほぼ問題が出ないようだし、これから述べるエラーが出るので EUC-JP を指定する利点はあまりないように思う。

さてそのエラーだが、ファイル名にチルダ記号(0x7e)が含まれている時に発生する。 20年ほど前、MS-DOSとWindows95との併用期に、ファイル名の長さの違いを回避するために MS-DOS側のファイル名にチルダが付与され「REPORT~1.TXT」のようになったりしたことを 覚えている人も多いだろう。今回はこのチルダが悪さをする。

まず、条件と現象を記すと、 mount_msdosfs -D sjis -L ja_JP.eucJP でマウントし、 たとえば REPORT~1.TXT が含まれるディレクトリに対して ls(1) を実行すると、

REPORT?1.TXT: No such file or directory

となる。

原因は、実は SJIS と EUC-JP では 0x7e に割り当てられている文字が違うことにある。 EUC-JP は 0x7f 以下の 7ビット部分の文字セットは ISO646-US で、これは 0x7e が ASCII と同じくチルダ記号になっている。 一方 SJIS は 7ビット文字セットが ISO646-JP で、こちらの 0x7e はチルダではなく 上付き横棒 ‾ (UTF8:0xe280be, UCS2:0x203E)なのである。 さらに、この上付き横棒は EUC-JP には存在しないため、EUC-JP に変換しようとすると EUC-JP の INVALID 記号である〓(いわゆるゲタ文字、EUC-JP:0xAEA2)に置換される。

このため、カーネル内部で次のような処理が行われる。 仮にファイル名を REPORT~1.TXT とすると、 まず SJIS から UCS2 を経由して EUC-JP に変換する。

SJIS:   52 45 50 4f 52 54   7e 31 2e 54 58 54
UCS2:   52 45 50 4f 52 54 203e 31 2e 54 58 54
EUC-JP: 52 45 50 4f 52 54 aea2 31 2e 54 58 54

カーネルはゲタ文字化した文字列をファイル名として保持する。 カーネルからファイルシステムにアクセスする時には逆変換が行われ、 ゲタ文字は INVALID記号ではなく普通の文字として変換される。

EUC-JP: 52 45 50 4f 52 54 aea2 31 2e 54 58 54
UCS2:   52 45 50 4f 52 54 3013 31 2e 54 58 54
SJIS:   52 45 50 4f 52 54 ac81 31 2e 54 58 54

MS-DOSファイルシステムのファイル名は 8.3式なのに 変換後のものは 9.3になっている。9文字目が切り捨てられて、 またエンディアンの関係か下位バイトが先になるようなので、結局

52 45 50 4f 52 54 81 ac 2e 54 58 54

というバイト列を探すことになるが、もちろんこんな名前のファイルは ディレクトリエントリに存在しないので、ENOENT になる。

原因が判ったところで対策だが、SJIS の 0x7e がそのまま 0x7e になるように改造する 方法を試してみた。FreeBSD のソースコードに手を入れて色々試したところ、

/usr/src/share/i18n/esdb/MISC/Shift_JIS.src
/usr/src/share/i18n/esdb/MISC/Shift_JIS-2004.src
/usr/src/lib/libkiconv/quirks.c

を修正すれば良いらしい。

上の2つはSJISの文字セットを定義しているところで、 ファイル内の ISO646-JP のところを ISO646-US に変更する。 quirks.c は文字変換テーブルから 0x7e のエントリをコメントアウトする。 それぞれビルドして、

/usr/share/i18n/esdb/MISC/Shift_JIS.esdb
/usr/share/i18n/esdb/MISC/Shift_JIS-2004.esdb
/lib/libkiconv.so.4

を置き換えてOSを再起動すれば、0x7e 文字の変換が抑制されて、エラーが出なくなった。

「最」のエラー

mount_msdosfs の -L オプションに ja_JP.UTF-8 を指定してもほとんど問題はなかったのだが、 唯一、ファイル名に「最」の字が含まれているとエラーが出た。

条件と現象を記すと、 mount_msdosfs -D sjis -L ja_JP.UTF-8 でマウントし、 たとえば 最初.TXT が含まれるディレクトリに対して ls(1) を実行すると、

最初.TXT: No such file or directory.

とエラーになる。

原因を調べた結果、 /usr/src/sys/libkern/iconv_xlat16.c:iconv_xlat16_toupper()でおかしな挙動が 起こっていることが判った。

MS-DOSはファイル名の大文字小文字を区別せず、ファイル名は大文字で格納される。 そのためカーネルからMS-DOSファイルシステムにアクセスする時に大文字化処理をしていて、 上記関数はその処理をする関数である。この関数は20行程度の短い関数なので、 実際に中を見た方が理解が早い。

static int
iconv_xlat16_toupper(void *d2p, int c)
{
        struct iconv_xlat16 *dp = (struct iconv_xlat16*)d2p;
        int c1, c2, out;

        if (c < 0x100) {
                c1 = C2I1(c << 8);
                c2 = C2I2(c << 8);
        } else if (c < 0x10000) {
                c1 = C2I1(c);
                c2 = C2I2(c);
        } else
                return (c);

        if (dp->d_table[c1] && dp->d_table[c1][c2] & XLAT16_HAS_UPPER_CASE) {
                out = dp->d_table[c1][c2] & 0xffff;
                if ((out & 0xff) == 0)
                        out = (out >> 8) & 0xff;
                return (out);
        } else
                return (c);
}

見て判る通り、1バイト文字はビットシフトして下位バイトを 0 にして、 後で逆方向にビットシフトして元に戻す処理をしている。 そのため、下位バイトが 0 のマルチバイト文字は 1バイト文字に間違えられてしまうのだ。

ここまで書けば想像がつくと思うが、くだんの「最」の字は、UCS2 のコードが 0x6700 なのである。 そのためこの関数においては 0x67 つまり ‘g’ と認識され、これが大文字化して 0x47 ‘G’ が 戻り値になる。

カーネルは G初.TXT というファイル名を探すが、ファイルシステムのディレクトリエントリには そのようなファイル名はもちろん存在しないため、ENOENT になる。

これを回避するため、私は以下のように修正した。

--- iconv_xlat16.c.orig     2018-06-22 08:02:52.000000000 +0900
+++ iconv_xlat16.c  2019-09-22 21:08:40.097838000 +0900
@@ -334,6 +334,8 @@
    } else if (c < 0x10000) {
                 c1 = C2I1(c);
                 c2 = C2I2(c);
+                if (c1 == 0)
+                        return (c);
    } else
            return (c);

この関数はカーネルモジュールの libiconv.ko に格納されるため、 libiconv.ko をリビルドして置き換えれば良い。 Makefile は /usr/src/sys/modules/libiconv/ にある。

予期せぬ副作用があるかも知れないので、mount_msdosfs を使用する時だけ libiconv.ko を入れ替えるようにする方が安全だろう。

USB ID

smartmontools の USB HDD 対応状況を調べている時に、 手持ちの HDD が Smartmontools USB Device SupportUSB ID Repository に 収録されていないことに気づいた。

折角なのでデータを送ろうかと思ったのだが、そうすると、 手持ちの USB機器を一通り調べて見たくなるのが凝り性の悪いところで、 すぐに出てくる場所にあった機器については下記の通り。 (他に、スキャナとプリンタが押入れにあるが、面倒なので調査対象から外した。 あとUSBメモリも除外)

種別 販売元・製品型番 USB ID USB デバイス名
HDD BUFFALO HD-LB2.0TU2 0411:01ba BUFFALO USB-SATA Bridge
HDD BUFFALO HDF-LB2.0TU2 0411:01ea BUFFALO USB-SATA Bridge
HDD BUFFALO HD-LC3.0U3-BKC 0411:027e BUFFALO HD-LCU3
HDD Logitec LHD-PBE40U2SV 0789:00cc Logitec LHD USB Device
DVD Panasonic LF-P968C 04da:0d14 Panasonic DVD-RAM MLT08
DVD BAFFALO DVSM-PL58U2 0411:00f9 BAFFALO USB2.0 External Mass Storage Device
SATA変換ケーブル GREEN HOUSE GH-USHD-SATA 152d:2339 JMicron JM20339 SATA Bridge
IDE変換ケーブル GREEN HOUSE GH-USHD-IDE 05e3:0702 Genesys Logic
有線LAN BUFFALO LUA3-U2-AGT 0411:006e BUFFALO
無線LAN PLANEX GW-US54GXS 2019:5303 PCI GW-US54GXS
キーボード SANWA Supply SKB-SL09BK 099a:6330 Zippy Technology Corp. USB Keyboard
有線マウス BUFFALO BSMOUK01 093a:2510  
無線マウス ELECOM M-LS14DL 25a7:2410 ARESON Laser mouse
無線マウス ELECOM M-BL2DB 056e:0092 ELECOM Wireless BlueLED Mouse
キーボード切替ケーブル BUFFALO BSKM201BK 2101:1402 ActionStar KM Switch
USBハブ ELECOM U2H-M4BWH 0409:005a (NEC Corp. High Speed Hub)
USBハブ ELECOM U2H-TAP3420SBK 0409:005a (NEC Corp. High Speed Hub)
カードリーダー ELECOM MR-GU2S04SV 0dda:2001 ICSI USB2.0 Card Reader
ヘッドホン BUFFALO 製品名不明 0c76:1607 JMTec, LLC. audio controller USB Headphone Set

販売ブランドと USB のベンダIDが違っているのは OEM 製品ということなのかな。

USB HDD (2)

過去に使用していた USB HDD についても smartctl の結果を記しておく。

BUFFALO HD-LB2.0TU2

テレビ用の1台目。2011年購入。

PC への接続時の dmesg は下記の通り。

ugen1.2: <BUFFALO USB-SATA Bridge> at usbus1
umass0: <BUFFALO USB-SATA Bridge, class 0/0, rev 2.00/1.12, addr 2> on usbus1
umass0:  SCSI over Bulk-Only; quirks = 0x8100
umass0:6:0:-1: Attached to scbus6
da0 at umass-sim0 bus 0 scbus6 target 0 lun 0
da0: <BUFFALO External HDD 0000> Fixed Direct Access SCSI-3 device
da0: 40.000MB/s transfers
da0: 1907729MB (3907029168 512 byte sectors)
da0: quirks=0x2<NO_6_BYTE>

smartctl の -d オプションをいろいろ変えて試してみたが、 結局うまく行かなかった。ドライブ自体には SMART があると思うのだが、 機器側の USB ブリッジが SMART に対応していないのか、 smartctl がこのブリッジに対応していないのか、どちらかだと思うが、 どちらなのかは判らない。

USB ブリッジの USB ID は 0x0411:0x01ba。 中のドライブの型番が読み出せないので中身は不明。

BUFFALO HDF-LB2.0TU2

テレビ用の2台目。確か2012年購入。

PC への接続時の dmesg は下記の通り。

ugen1.2: <BUFFALO USB-SATA Bridge> at usbus1
umass0: <BUFFALO USB-SATA Bridge, class 0/0, rev 2.00/1.00, addr 2> on usbus1
umass0:  SCSI over Bulk-Only; quirks = 0xc101
umass0:6:0:-1: Attached to scbus6
da0 at umass-sim0 bus 0 scbus6 target 0 lun 0
da0: <BUFFALO External HDD 0000> Fixed Direct Access SCSI-3 device
da0: 40.000MB/s transfers
da0: 1907729MB (3907029168 512 byte sectors)
da0: quirks=0x2<NO_6_BYTE>

smartctl の自動推測がうまく働き、-d オプションの指定なしでもうまく行った。 内部的には -d sat を使用しているようだ。

# smartctl -i /dev/da0
=== START OF INFORMATION SECTION ===
Model Family:     Western Digital Green
Device Model:     WDC WD20EARX-00PASB0
Serial Number:    WD-WCAZAE534488
LU WWN Device Id: 5 0014ee 25c684be6
Firmware Version: 51.0AB51
User Capacity:    2,000,398,934,016 bytes [2.00 TB]
Sector Sizes:     512 bytes logical, 4096 bytes physical
Device is:        In smartctl database [for details use: -P show]
ATA Version is:   ATA8-ACS (minor revision not indicated)
SATA Version is:  SATA 3.0, 6.0 Gb/s (current: 1.5 Gb/s)
Local Time is:    Tue May  8 15:24:43 2018 JST
SMART support is: Available - device has SMART capability.
SMART support is: Enabled

中身は WD の緑のようだ。

USB ブリッジの USB ID は 0x0411:0x01ea。

Logitec LHD-PBE40U2SV

ポータブルHDD。いつ頃の購入だったか覚えていないが、 容量 40GB なので、だいぶ昔だということは間違いない。 15年前くらいか?

PC への接続時の dmesg は下記の通り。

ugen1.2: <Logitec Corp. LHD USB Device> at usbus1
umass0: <Logitec Corp. LHD USB Device, class 0/0, rev 2.00/1.16, addr 2> on usbus1
umass0:  SCSI over Bulk-Only; quirks = 0x0000
umass0:6:0:-1: Attached to scbus6
da0 at umass-sim0 bus 0 scbus6 target 0 lun 0
da0: <LOGITEC LHD-PBEU2 1.16> Fixed Direct Access SCSI device
da0: Serial Number 0406122020000689
da0: 40.000MB/s transfers
da0: 37954MB (77730300 512 byte sectors)
da0: quirks=0x2<NO_6_BYTE>
cd1 at umass-sim0 bus 0 scbus6 target 0 lun 1
cd1: <LOGITEC VIRTUAL CD-ROM 1.16> Removable CD-ROM SCSI device
cd1: Serial Number 0406122020000689
cd1: 40.000MB/s transfers
cd1: 109MB (55983 2048 byte sectors)
cd1: quirks=0x10<10_BYTE_ONLY>

da の他に cd デバイスが認識されるのは、製品のマニュアルを 仮想CD-ROM として内部に格納しているからだ。

-d オプションを指定しても、smartctl からは内部が見えなかった。

USB ID は 0789:00cc。

GREEN HOUSE GH-USHD-SATA

これは HDD ではなく、SATA の生ドライブを PC に接続するための変換ケーブル。 購入時期はよく覚えていないが、IDE用のケーブルと一緒に買ったので、 HDD のインターフェイスが IDE から SATA に切り替わりつつある頃だろう。

試しに使い古しの生ドライブを接続すると、下記のような dmesg が出る。

ugen1.2: <JMicron USB to ATAATAPI Bridge> at usbus1
umass0: <MSC Bulk-Only Transfer> on usbus1
umass0:  SCSI over Bulk-Only; quirks = 0xc100
umass0:6:0:-1: Attached to scbus6
da0 at umass-sim0 bus 0 scbus6 target 0 lun 0
da0: <WDC WD80 0JD-08LSA0 > Fixed Direct Access SCSI-2 device
da0: Serial Number DA9A535973FF
da0: 40.000MB/s transfers
da0: 76324MB (156312576 512 byte sectors)
da0: quirks=0x2<NO_6_BYTE>

製品の中身は JMicron 製のようだ。 smartctl の自動推測が効いて、-d オブション無しで SMART が見られる。 内部的には -d usbjmicron が使用されているようだ。

USB ID は 152d:2339。

Tinkerer 1.7 と Python 2

新しく Tinkerer 1.7 をセットアップしたのはいいが、ビルドが失敗する。

$ tinker -b
Running Sphinx v1.7.4

Extension error:
Could not import extension tinkerer.ext.blog (exception: No module named builtins)

情報が足りないので、python 上で import を実行してみる。

$ python
Python 2.7.14 (default, Sep 27 2017, 12:15:00)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import tinkerer.ext.blog
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/maita/manuscript/webdocs/venv/lib/python2.7/site-packages/tinkerer/ext/blog.py", line 11, in <module>
    from tinkerer.ext import (aggregator, author, filing, html5, metadata, patch,
  File "/Users/maita/manuscript/webdocs/venv/lib/python2.7/site-packages/tinkerer/ext/aggregator.py", line 12, in <module>
    from tinkerer.ext.uistr import UIStr
  File "/Users/maita/manuscript/webdocs/venv/lib/python2.7/site-packages/tinkerer/ext/uistr.py", line 12, in <module>
    import builtins as __builtin__
ImportError: No module named builtins

エラーの真の出所は tinkerer.ext.uistr。Python のビルトインモジュールは、 Python 2 では __builtin__ という名前だが Python 3 で builtins に変わった。 該当のコードは Python 3 で実行した場合にその違いを吸収するためのものだろうが、 Python 2.7 で実行すると逆に builtins モジュールが無いので、ImportError が出るのは当然だ。

uistr.py を直接修正するのが一番簡単だろう。

#import builtins as __builtin__
import __builtin__

面倒だが、まぁ、未だに Python 2 を使い続けている自分が悪いのだ。

USB HDD の SMART を確認する

テレビの録画に使用していた USB HDD がエラーを出すようになった。 テレビ本体は 2011年購入の東芝REGZAなのだが、備忘のために症状を詳しく記すと、

  • 予約した録画が開始せず、クイックメニュー > お知らせ > 本機に関するお知らせに「録画機器の接続を確認できなかったため、録画予約を中止しました」というメッセージが追加される。
  • 録画実行中に録画が停止し、クイックメニュー > お知らせ > 本機に関するお知らせに「録画機器にエラーが発生したため、録画予約を中止しました」というメッセージが追加される。
  • 録画再生中に「この操作はできません」と一瞬表示されたあと、画面が真っ暗になる。

HDD のエラーと言うとまず不良セクタを疑う。 その場合は、録画再生で必ず同じ場所でエラーが出る筈だが、 今回の場合はそうではなく、エラーが出る場所がまちまちだったので、 その可能性は低いと思われた。

それよりも、電源や駆動系の劣化によってパフォーマンスが低下し、 スピンアップやヘッドシークが遅くなったりして、 リアルタイム処理に追いつかなくなってるような感じがした。

とは言え、こういう問題について「感じがした」なんていう 印象論を語っても意味はない。不良セクタの有無はディスクの SMART を確認すれば判るのだから、 HDD を PC に接続して実際に SMART を見てみることにした。

PC は FreeBSD 10.4R に pkg で smartmontools 6.6 をインストールしたもの。

HDD は BUFFALO DriveStation HD-LC3.0U3-BKC。確か2015年に購入。

まず、HDD の USB ケーブルを PC に挿した時にコンソールに表示されるメッセージは下記の通り。

ugen1.2: <BUFFALO HD-LCU3> at usbus1
umass0: <BUFFALO HD-LCU3, class 0/0, rev 2.10/1.00, addr 2> on usbus1
umass0:  SCSI over Bulk-Only; quirks = 0xc101
umass0:6:0:-1: Attached to scbus6
da0 at umass-sim0 bus 0 scbus6 target 0 lun 0
da0: <BUFFALO External HDD 0000> Fixed Direct Access SCSI-3 device
da0: 40.000MB/s transfers
da0: 2861588MB (5860533168 512 byte sectors)
da0: quirks=0x2<NO_6_BYTE>

smartctl はデバイスの種類を自動的に推測する機能があるが、 この製品ではうまく推測できないようで、以下のような結果になる。

# smartctl -i /dev/da0
smartctl 6.6 2017-11-05 r4594 [FreeBSD 10.4-RELEASE-p8 amd64] (local build)
Copyright (C) 2002-17, Bruce Allen, Christian Franke, www.smartmontools.org

/dev/da0: Unknown USB bridge [0x0411:0x027e (0x100)]
Please specify device type with the -d option.

USB HDD は中身に SATA ドライブを使用していることが多いようなので、 -d sat を指定してみると、やはり正常にアクセスできるようになった。 (以下、smartctl の出力のコピーライト部分は省略)

# smartctl -i -d sat /dev/da0
=== START OF INFORMATION SECTION ===
Model Family:     Toshiba 3.5" DT01ACA... Desktop HDD
Device Model:     TOSHIBA DT01ACA300
Serial Number:    25PHEW5GS
LU WWN Device Id: 5 000039 ff4f14c2b
Firmware Version: MX6OABB0
User Capacity:    3,000,592,982,016 bytes [3.00 TB]
Sector Sizes:     512 bytes logical, 4096 bytes physical
Rotation Rate:    7200 rpm
Form Factor:      3.5 inches
Device is:        In smartctl database [for details use: -P show]
ATA Version is:   ATA8-ACS T13/1699-D revision 4
SATA Version is:  SATA 3.0, 6.0 Gb/s (current: 3.0 Gb/s)
Local Time is:    Tue May  8 09:12:06 2018 JST
SMART support is: Available - device has SMART capability.
SMART support is: Enabled

中身は東芝製ドライブのようだ。SMART をサポートしているようなので、 まずはエラーログを見てみる。

# smartctl -d sat -l error /dev/da0
SMART Error Log Version: 1
No Errors Logged

エラーは出ていない。次に -A オプションで attribute を確認する。

# smartctl -d sat -A /dev/da0
=== START OF READ SMART DATA SECTION ===
SMART Attributes Data Structure revision number: 16
Vendor Specific SMART Attributes with Thresholds:
ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE      UPDATED  WHEN_FAILED RAW_VALUE
  1 Raw_Read_Error_Rate     0x000b   100   100   016    Pre-fail  Always       -       0
  2 Throughput_Performance  0x0005   140   140   054    Pre-fail  Offline      -       67
  3 Spin_Up_Time            0x0007   134   134   024    Pre-fail  Always       -       413 (Average 439)
  4 Start_Stop_Count        0x0012   099   099   000    Old_age   Always       -       6336
  5 Reallocated_Sector_Ct   0x0033   100   100   005    Pre-fail  Always       -       0
  7 Seek_Error_Rate         0x000b   100   100   067    Pre-fail  Always       -       0
  8 Seek_Time_Performance   0x0005   124   124   020    Pre-fail  Offline      -       33
  9 Power_On_Hours          0x0012   099   099   000    Old_age   Always       -       7029
 10 Spin_Retry_Count        0x0013   100   100   060    Pre-fail  Always       -       0
 12 Power_Cycle_Count       0x0032   099   099   000    Old_age   Always       -       4844
192 Power-Off_Retract_Count 0x0032   095   095   000    Old_age   Always       -       6336
193 Load_Cycle_Count        0x0012   095   095   000    Old_age   Always       -       6336
194 Temperature_Celsius     0x0002   222   222   000    Old_age   Always       -       27 (Min/Max 3/62)
196 Reallocated_Event_Count 0x0032   100   100   000    Old_age   Always       -       0
197 Current_Pending_Sector  0x0022   100   100   000    Old_age   Always       -       0
198 Offline_Uncorrectable   0x0008   100   100   000    Old_age   Offline      -       0
199 UDMA_CRC_Error_Count    0x000a   200   200   000    Old_age   Always       -       0

ID 5 や ID 196 から明らかなように、セクタの代替処理がまったく発生していない。 その他の項目も、これと言って悪いところはなさそうに見える。 やはりディスク自体はまあまあ健全で、少なくともデータロストの危険はなさそうだ。

念のため、SMART の自己テストを実行してみる。short テストは 1,2分、long テストは 6時間ちょっと掛かった。

# smartctl -d sat -l selftest /dev/da0
=== START OF READ SMART DATA SECTION ===
SMART Self-test log structure revision number 1
Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error
# 1  Extended offline    Completed without error       00%      7035         -
# 2  Short offline       Completed without error       00%      7029         -

やはりエラーなし。

となるとエラーの原因は、電源かUSBケーブルかインターフェイス部か、ということになるが、 これ以上の原因特定は難しそうなので(エラー自体の再現性が低いので)、 結局新しい USB HDD を購入して交換することにしたのであった。

QGISで表示できないshapefile

外部の人から受け取った SHAPE 形式のGISデータを、 QGISで見てみようとしたところ、データはちゃんと入っているっぽいのに 画面には何も表示されない、という状況になり、 原因調査にちょっと時間が掛かったのでメモしておく。

原因

先に結論を述べると、地物のジオメトリが緯度経度座標なのに、 bounding box (以下BBOX)がウェブメルカトル(EPSG3857)座標になっていたのが原因。

受け取ったデータはポリゴンデータだったのだが、 ポリゴンデータのような、複数の座標を持つジオメトリ型の場合、 SHAPEファイル内には、

  • レイヤ全体のBBOX
  • 個々のジオメトリごとのBBOX

が保持される。詳しくは ESRI の解説書 を見よ。

今回のケースでは、レイヤ全体のBBOXはジオメトリデータと同じく 緯度経度座標になっていたのだが、ジオメトリごとのBBOXは 何故か EPSG3857 になっていた。 おそらくQGISは、ジオメトリごとのBBOXを律儀に適用し、 地物の表示をマスクしてしまった、ということだと思われる。

症状

原因調査に時間が掛かったのは、SHAPEファイルを読み込ませた時に QGISが何もエラーを出さなかったからだ。

同様に、ogrinfo もエラーを出さなかった。 ogrinfo を実行すると Extent という表記でBBOXを表示するが、 これはSHAPEファイル内の「レイヤ全体のBBOX」の数値で、 ジオメトリごとのBBOXはまったく出力されない。

原因特定

ogr2ogr を使って SHAPE形式からSHAPE形式に変換したところ、 QGISで表示できるようになったので、バイナリ用diffを使って 変化した部分を特定し、ESRIの説明書と突き合わせて ジオメトリごとのBBOXが不適切であることを突き止めた。

HEX表記の浮動小数点データを10進表記にする作業には、 python の struct モジュールを使用した。

他の環境関係法令による政令市

指定都市等のリストは 総務省 を参照するのが確実かな。

さて、いろいろややこしい。

水質汚濁防止法

水質汚濁防止法施行令 第10条に、指定都市と中核市に加えて

  • 福島市
  • 市川市
  • 松戸市
  • 市原市
  • 町田市
  • 藤沢市
  • 徳島市

が挙げられている。

特例市については、かつては令第10条に記載されていたが、 地方自治法改正に合わせて条文から削除し、 H27.1.30付の附則で経過措置として施行時特例市に従来通りの事務委任を 続けるという規定が定められている。

だから実質的には、指定都市、中核市、施行時特例市、上記7市に 水質汚濁防止法の事務が委任されている。

騒音規制法

H23年度までは、(旧)騒音規制法施行令第4条第1項において、

  • 一関市
  • 日立市
  • 土浦市
  • ひたちなか市
  • 桐生市
  • 松戸市
  • 君津市
  • 上田市
  • 多治見市

に、自動車騒音常時監視の事務が委任されていた (H21年度版環境六法で確認)。

指定都市、中核市、特例市、特別区には自動車騒音常時監視に加えて 他の事務も委任されていた(旧令4-2)。

H24年度からは、地方自治の推進のため、すべての一般市にも 指定都市に対するものと同様の事務が委任されるようになった。 それに合わせて令第4条は削除された。

だから現在では、騒音規制法に定める政令市、というものはない。

振動規制法

H23年度までは、(旧)振動規制法施行令第5条において、 指定都市、中核市、特例市、特別区 に事務が委任されていた。

H24年度からは、地方自治の推進のため、すべての一般市にも 指定都市に対するものと同様の事務が委任されるようになった。 それに合わせて令第5条は削除された。

だから現在では、振動規制法に定める政令市、というものはない。

悪臭防止法

H23年度までは、(旧)悪臭防止法施行令第3条において、 指定都市、中核市、特例市、特別区 に事務が委任されていた。

H24年度からは、地方自治の推進のため、すべての一般市にも 指定都市に対するものと同様の事務が委任されるようになった。 それに合わせて令第3条は削除された。

だから現在では、悪臭防止法に定める政令市、というものはない。

ダイオキシン類対策特別措置法

ダイオキシン類対策特別措置法施行令 第8条に、指定都市と中核市に対する事務委任が規定されている。

特例市に関しては、もとから規定がなかったようだ (H21年度版環境六法で確認)。

大気汚染防止法に定める政令市

法律に疎いと何処に書いてあるのかわからんのよね。

大気汚染防止法施行令 第13条に規定。

  • 小樽市
  • 室蘭市
  • 苫小牧市
  • 川口市
  • 所沢市
  • 市川市
  • 松戸市
  • 市原市
  • 平塚市
  • 藤沢市
  • 四日市市
  • 吹田市
  • 八尾市
  • 明石市
  • 加古川市
  • 大牟田市

他に、指定都市、中核市、施行時特例市に事務の委譲がある。

川口市、所沢市、平塚市、四日市市、吹田市、八尾市、明石市、加古川市は 大気汚染防止法政令市かつ施行時特例市。

環境省・大気汚染防止法施行状況調査報告書 の末尾に政令市等の一覧がある。

いわゆる市区町村コード(2)

(承前)

データとしての使いやすさと情報量という点では、 統計LOD から公開されている「標準地域コード」で充分である。 ただ、実際に中身を見たところ、やや不十分なところがあった。

  • 自治体の分割に対応していない。山梨県上九一色村が2分割されて 甲府市と富士河口湖町に編入されたケースなどが正確に記述されていない。
  • 標準地域コード策定時点(1970年4月1日)時点で既に廃止されていた自治体の エンティティが混入していて紛らわしい。

これについては統計LODの問い合わせアドレスにメールで問い合わせたところ、 データを修正する方向で対応する、とのことであった。 そのうちデータがアップデートされるものと思われる。

さて LOD と言えば Sparql とかいう言語で検索したりするものらしいが、 私は当面は自発的にこれを学ぶつもりはない(だってめんどくさそうだし)。 この程度のデータ量だったらどんな方法でも大差はないのだから、 いつも通りに Python + rdflib で スクリプト を書いてみた。 各コードの有効期間と編入・合併先を抽出するスクリプトである。

実行結果は こんな感じ (ただし別途 sort してある)。 元データのライセンスが CC-BY 4.0 なので、このファイルを利用する場合は 元データの権利者を表示すること。 ただし標準地域コードの著作権者が誰になるのか、統計LODのサイトに 記載が無い…(追記・「政府統計の総合窓口(e-Stat)」で良いそうだ)。

いわゆる市区町村コードなるもの

これまで行政由来のデータを扱うことがほとんど無かったので 「市区町村コード」とかいうものをほとんど知らなかったのであるが、 業務上避けて通れなくなったので、これを機会に真面目に調べることにしたのである。

いわゆる市区町村コードには、実は3系統あって、初心者は混乱する。 というか混乱した。系統ごとに用語が微妙に違うので google で検索に 巧く引っかからなかったりするようだ。

系統間の関係について、調べた中では ここ が解りやすいか。

自治省系コード

正式には「全国地方公共団体コード」の「都道府県コード及び市区町村コード」。 旧自治省が地方自治体の事務処理のために作成したもので、作成時期は 3系統の中で一番古く、1968年。以下「市区町村コード」と呼ぶ。

「全国地方公共団体コード」は「市区町村コード」の他に 「一部事務組合等コード」が含まれている。この「一部事務組合等コード」が どういうものかと言うと、 たとえば「常総地方広域市町村圏事務組合」のような団体に 事務処理用コードを割り当てるためのものだ。 今回はこのような組合のことは関係ないので立ち入らないことにする。

「市区町村コード」のコードとしての特徴は、

  • 検査数字を含めた 6桁の数値
  • 都道府県、市町村、特別区(東京23区)、政令指定都市の行政区、を収録 (郡、支庁・振興局は除外)

現在は総務省地域力創造グループが管理しており、 総務省の電子自治体のページ から PDF と Excel がダウンロードできる。

過去の情報は、2005年4月1日以降の改正分が同じく PDF と Excel で入手できる。 それ以前の情報について、直接問い合わせところ、総務省地域力創造グループでは データを持っておらず、別途資料を当たられたい、とのことで、 例として 第一法規(という出版社)「全国都道府県・市区町村コード」 を紹介された。

行政管理庁系コード

正式には「統計に用いる標準地域コード」。 旧行政管理庁が 1970年に策定したもので、現在は総務省統計局が管理している。

コードの割り当て規則は、先行の自治省系コードのものを流用しているので、 実態としてはほとんど同じと言って良いが、細かいところで差異がある。

  • 検査数値なしの 5桁
  • 郡、支庁・振興局にもコードを割り当てている。
  • 策定の時点で「削除」扱いになっているコードがある。つまり、 1970年4月1日以前に存在し、先行の自治省系コードが割り当てられていた自治体が、 1970年4月1日までに廃止された場合、自治省系コードとの整合性のため、 そのコードを削除扱いにした、ということ。

電子情報としては、 総務省ウェブサイトの統計部門のページ から PDF, CSV で入手できるが、 2016年3月31日に LOD形式で公開された ので、現在はこれを使うのが良い。 1970年4月1日から現在までの変遷が追跡できる(ただし、一部に誤りがある。後述)。

このページ の一番下に turtle 形式のファイルが置いてある。

JIS コード

正式には JIS X401, JIS X402。 自治省系「市区町村コード」を流用して JIS 規格化したもの、のようだ。 これについて調べる前に、「標準地域コード」の LOD が見付かったので、 調べるのを止めてしまった。

(続く)