FSDirWalkerを公開しました (3) [ソフトウェア]
今回は、MAX_PATHを超えたパスを扱いたい件です。
Windowsエクスプローラなどでパスを扱う場合、多くは最大長をMAX_PATHとしています。
MAX_PATHは260と定義されているので、パスは最大260文字(大抵はC言語で扱うので文字列末尾の\0を除くと259文字まで)となります。
しかし、NTFSの様なファイルシステムではMAX_PATHをはるかに超える32767文字(最大16383階層程度)までのパスを扱うことができます。
私は「大深度パス」などと勝手に呼んでますが、Windows標準ではこの長いパスを扱えるツールが無いことが、FSDirWalkerを作成した理由のひとつです。
プログラムから扱う場合は、Win32 APIに渡すファイルパスに"\\?\"プリフィックス(また出ました)を付けて呼び出すことでMAX_PATHの制限を回避できます。
ですから、最初は扱うファイルパスにプリフィックスを付けて、32K文字を格納できるバッファを用意すれば実現できるのではと思いましたが、案の定それほど単純な話ではありませんでした。
32767文字というのは、ネイティブAPIが扱うパス文字列がUNICODE_STRING構造体で表現される事に由来しています。
UNICODE_STRINGはワイド文字列へのポインタと長さの情報で構成されます。LengthはUSHORTですので0~65535まで表現できますが、これは文字数ではなく文字列を格納するのに必要なバイト数になっています。ワイド文字は1文字が2バイトですので、実質上の最大値は65534バイト、つまり32767文字が最大文字数になります。
当初は漠然とボリューム名とファイルのパスを合わせて32767文字だと思っていたのですが、ある時、更に下の階層にファイルやディレクトリが存在するはずなのにパス文字数が32767文字に達してしまって参照できないケースがあることに気づきました。そこでよくよく調べてみると、Windowsのファイルシステムではボリュームデバイスとボリューム内のファイル(パス)と分けて管理していることが判りました。つまり、ボリューム名を含まないファイルへのパスだけで32767文字使えるのです。
これは厄介です。いままでWin32でのファイル操作を含め、フルパス文字列のみで一意にボリューム上のファイルやディレクトリを識別してオープンできたものが、最低でもボリュームとファイル(パス)に分けてオープンしなければならないことを意味するからです。
ファイルをオープンしたり、ディレクトリ内のファイルを列挙する際にすべて「分割オープン」しないと「ディレクトリの底」到達できないのです。これは、私の知る限りWin32では実現できません。
この問題は、NTDLLが提供するネイティブAPIを使用することでクリアしました。もともと別の理由で既にネイティブAPIを常用していたので、その点の移行は簡単でした。
ネイティブAPIのNtCreateFile/NtOpenFileは引数として親オブジェクトのハンドルを指定できるので、それを利用することにします。あるファイルへのフルパスを受け取ってオープンする場合、まずボリュームのルートディレクトリをオープンしてハンドルを手に入れた後、それを親ハンドルにして残りをルート相対パスとして別途オープンするのです。この方法であれば、32767文字パスの「底」まで到達できます。
そうなると、「底」のディレクトリを親としてオープンし、そこから更に下の階層にファイルやディレクトリを作成できそうな感じがしましたが、それはエラーが返され実行できませんでした。USHORTによる上限とは別に、内部的に文字数の上限をチェックしているのかもしれません。
参照に関しては解決しましたが、「大深度ディレクトリ」には解決できていない問題があります。
ひとつはディレクトリのコピーや削除などを処理する際、入れ子になっているサブディレクトリの処理のために関数を再帰的に呼び出すことがあります。その際にリソースの上限に達してしまうことです。
具体的にはスタックとハンドルです。スタックはディレクトリを再帰する度に消費され、ネストが深い場合スタックオーバーフローを起こします。
スタックサイズを大きく取れば使い切ることはありませんが、どの程度確保すればよいか塩梅が難しく他の部分でのスタック消費量と調整が必要です。
ハンドルの方は32bit版で顕在化します。やはり再帰ごとにディレクトリ内容を列挙するためにディレクトリのオープンを繰り返しハンドルを取得しますが、やはりネストが深くなるとプロセスに割り当てられたハンドルを使い切ってしまうことです。
テスト用の小さなスタブでも使い切ってしまうので、実際のアプリケーションではスタブよりも早く使い切ってしまうでしょう。こちらはプロセスのハンドルテーブルを拡張する方法も知りませんし、ディレクトリをオープンしないで(または毎回クローズして)ネストを処理する方法も思いつきませんでしたので、解決できていません。そのため、現状ではコピーや削除で処理できる階層を1024階層までと制限する仕様になっています。
(なお、ハンドルの方は、64bit環境では十分なハンドル領域があるので深い階層でも処理できる様ですが、現在のFSDirWalkerは32bit版と同じ制限としています)。
もうひとつは、ディレクトリツリー表示です。当然何万階層もツリービューコントロールで表示できる筈も無く、したがって現在はルートから253階層までの表示となっています。
ツリービューコントロールは254階層あたりから階層レベルを折り返して(右に伸びたツリーが左に戻って)表示される様です。このあたりを制御可能なのか不明でしたので制限となっていますが、そもそも百階層程度のツリー表示でも見難いので悩ましいところです。
(続く)
Windowsエクスプローラなどでパスを扱う場合、多くは最大長をMAX_PATHとしています。
MAX_PATHは260と定義されているので、パスは最大260文字(大抵はC言語で扱うので文字列末尾の\0を除くと259文字まで)となります。
しかし、NTFSの様なファイルシステムではMAX_PATHをはるかに超える32767文字(最大16383階層程度)までのパスを扱うことができます。
私は「大深度パス」などと勝手に呼んでますが、Windows標準ではこの長いパスを扱えるツールが無いことが、FSDirWalkerを作成した理由のひとつです。
―◇―
プログラムから扱う場合は、Win32 APIに渡すファイルパスに"\\?\"プリフィックス(また出ました)を付けて呼び出すことでMAX_PATHの制限を回避できます。
ですから、最初は扱うファイルパスにプリフィックスを付けて、32K文字を格納できるバッファを用意すれば実現できるのではと思いましたが、案の定それほど単純な話ではありませんでした。
32767文字というのは、ネイティブAPIが扱うパス文字列がUNICODE_STRING構造体で表現される事に由来しています。
struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING; |
当初は漠然とボリューム名とファイルのパスを合わせて32767文字だと思っていたのですが、ある時、更に下の階層にファイルやディレクトリが存在するはずなのにパス文字数が32767文字に達してしまって参照できないケースがあることに気づきました。そこでよくよく調べてみると、Windowsのファイルシステムではボリュームデバイスとボリューム内のファイル(パス)と分けて管理していることが判りました。つまり、ボリューム名を含まないファイルへのパスだけで32767文字使えるのです。
これは厄介です。いままでWin32でのファイル操作を含め、フルパス文字列のみで一意にボリューム上のファイルやディレクトリを識別してオープンできたものが、最低でもボリュームとファイル(パス)に分けてオープンしなければならないことを意味するからです。
ファイルをオープンしたり、ディレクトリ内のファイルを列挙する際にすべて「分割オープン」しないと「ディレクトリの底」到達できないのです。これは、私の知る限りWin32では実現できません。
この問題は、NTDLLが提供するネイティブAPIを使用することでクリアしました。もともと別の理由で既にネイティブAPIを常用していたので、その点の移行は簡単でした。
ネイティブAPIのNtCreateFile/NtOpenFileは引数として親オブジェクトのハンドルを指定できるので、それを利用することにします。あるファイルへのフルパスを受け取ってオープンする場合、まずボリュームのルートディレクトリをオープンしてハンドルを手に入れた後、それを親ハンドルにして残りをルート相対パスとして別途オープンするのです。この方法であれば、32767文字パスの「底」まで到達できます。
そうなると、「底」のディレクトリを親としてオープンし、そこから更に下の階層にファイルやディレクトリを作成できそうな感じがしましたが、それはエラーが返され実行できませんでした。USHORTによる上限とは別に、内部的に文字数の上限をチェックしているのかもしれません。
―◇―
参照に関しては解決しましたが、「大深度ディレクトリ」には解決できていない問題があります。
ひとつはディレクトリのコピーや削除などを処理する際、入れ子になっているサブディレクトリの処理のために関数を再帰的に呼び出すことがあります。その際にリソースの上限に達してしまうことです。
具体的にはスタックとハンドルです。スタックはディレクトリを再帰する度に消費され、ネストが深い場合スタックオーバーフローを起こします。
スタックサイズを大きく取れば使い切ることはありませんが、どの程度確保すればよいか塩梅が難しく他の部分でのスタック消費量と調整が必要です。
ハンドルの方は32bit版で顕在化します。やはり再帰ごとにディレクトリ内容を列挙するためにディレクトリのオープンを繰り返しハンドルを取得しますが、やはりネストが深くなるとプロセスに割り当てられたハンドルを使い切ってしまうことです。
テスト用の小さなスタブでも使い切ってしまうので、実際のアプリケーションではスタブよりも早く使い切ってしまうでしょう。こちらはプロセスのハンドルテーブルを拡張する方法も知りませんし、ディレクトリをオープンしないで(または毎回クローズして)ネストを処理する方法も思いつきませんでしたので、解決できていません。そのため、現状ではコピーや削除で処理できる階層を1024階層までと制限する仕様になっています。
(なお、ハンドルの方は、64bit環境では十分なハンドル領域があるので深い階層でも処理できる様ですが、現在のFSDirWalkerは32bit版と同じ制限としています)。
もうひとつは、ディレクトリツリー表示です。当然何万階層もツリービューコントロールで表示できる筈も無く、したがって現在はルートから253階層までの表示となっています。
ツリービューコントロールは254階層あたりから階層レベルを折り返して(右に伸びたツリーが左に戻って)表示される様です。このあたりを制御可能なのか不明でしたので制限となっていますが、そもそも百階層程度のツリー表示でも見難いので悩ましいところです。
(続く)
2015-07-01 23:39
コメント(0)
コメント 0