MAX_PATHに関する考察とか

なんとなく悩まされたので悩んだ過程と私なりの結論とかを書いてみます。 考察なんで嘘書いてます。(言いきってどうする)。 嘘発見情報や追加情報などありましたらぜひご連絡ください。

広い考察をしたいくせに確認できるプラットホームがあまりに少ないのが 問題です。補完できるときに補完したい...

ここで「問題」としているもののは、そもそもそういうふうに使われることを 意図して作られていないものを無理に使っているから、と言えるような 気もしますが...

MAX_PATHとは

まずはMSDN Libraryの解説をごらんください。

この定数はstdlib.hにあると書いてあります。 確かに、

にはありますが、少なくとも には見当たりません。また、VC++などでは_MAX_PATHといった風に 前にアンダースコアがつくようです。mingw32では MAX_PATHは_MAX_PATHの別名になっているようです。

MSDN Libraryでは、「Microsoft独自の仕様」の場合は そのことを明記しているわけですが、ここで出てくる定数には そのような表記はありません...謎だ。アンダースコアがつくということは DOS時代のファンクションリクエストの仕様をひっぱってるのか?

とりあえずそれがどのような値を持つのか、実際に表示させてみました。 私の環境(Windows2000 SP1)では

でした。

その他の定数

他にもFILENAME_MAX(@stdio.h) というものがあります。手持ちの環境では... まずはFreeBSDNetBSD

stdio.h:#define FILENAME_MAX    1024    /* must be >= PATH_MAX <sys/syslimits.h>
 */
続いてMingw32
*
 * The maximum length of a file name. You should use GetVolumeInformation
 * instead of this constant. But hey, this works.
 *
 * NOTE: This is used in the structure _finddata_t (see io.h) so changing it
 *       is probably not a good idea.
 */
#define FILENAME_MAX    (260)
Cygwin
#ifdef __FILENAME_MAX__
#define FILENAME_MAX    __FILENAME_MAX__
#else
#define FILENAME_MAX    1024
#endif
最後にVC++
#define FILENAME_MAX    260
などということになっています。

あとは...FreeBSDやNetBSDでは、各種MIN/MAXの定数は /usr/include/limits.hから辿れるところにまとめられているのですが、

(NetBSD,FreeBSD) #define _POSIX_PATH_MAX         255
(Cygwin) #define PATH_MAX (260 - 1 /*NUL*/)
(VC++) #define _POSIX_PATH_MAX     255
(Mingw32) 該当なし... そのかわり(?)
*
 * File system limits
 *
 * TODO: NAME_MAX and OPEN_MAX are file system limits or not? Are they the
 *       same as FILENAME_MAX and FOPEN_MAX from stdio.h?
 * NOTE: Apparently the actual size of PATH_MAX is 260, but a space is
 *       required for the NUL. TODO: Test?
 */
#define PATH_MAX        (259)

VC++は_POSIX_が定義されているときだけ出現するようです。 NetBSD/FreeBSDでは_ANSI_SOURCEが定義されているときには 出現しないようです。

どのように使われているのか

ちょっと手元のマシン(FreeBSD 4.3-RELEASE)をいじめてみました。(ごめんよ)

% cd /usr/src && find -name '*.[ch]' -exec grep -l -H FILENAME_MAX \{\} \; 
./bin/sh/main.c
./contrib/less/lsystem.c
./contrib/libio/stdio/stdio.h
./gnu/lib/libstdc++/_G_config.h
./gnu/usr.bin/man/man/man.c
./include/stdio.h
./lib/libc/stdtime/localtime.c
./lib/libc/stdtime/private.h
./lib/libdisk/create_chunk.c
./release/sysinstall/command.c
./release/sysinstall/config.c
./release/sysinstall/devices.c
./release/sysinstall/disks.c
./release/sysinstall/dmenu.c
./release/sysinstall/install.c
./release/sysinstall/installUpgrade.c
./release/sysinstall/label.c
./release/sysinstall/misc.c
./release/sysinstall/msg.c
./release/sysinstall/package.c
./release/sysinstall/sysinstall.h
./release/sysinstall/system.c
./usr.sbin/btxld/btxld.c
./usr.sbin/i4b/isdnd/isdnd.h
./usr.sbin/i4b/isdnd/monitor.c
./usr.sbin/pkg_install/add/extract.c
./usr.sbin/pkg_install/add/main.c
./usr.sbin/pkg_install/add/perform.c
./usr.sbin/pkg_install/create/main.c
./usr.sbin/pkg_install/create/perform.c
./usr.sbin/pkg_install/create/pl.c
./usr.sbin/pkg_install/delete/perform.c
./usr.sbin/pkg_install/info/main.c
./usr.sbin/pkg_install/info/perform.c
./usr.sbin/pkg_install/info/show.c
./usr.sbin/pkg_install/lib/file.c
./usr.sbin/pkg_install/lib/pen.c
./usr.sbin/pkg_install/lib/plist.c
./usr.sbin/pkg_install/lib/str.c
./usr.sbin/pkg_install/lib/deps.c
以外というか予想通りというか...あまり使われてませんな。 次は_POSIX_PATH_MAX
find . -name "*.[ch]" -exec grep -l -H _POSIX_PATH_MAX \{\} \;
./contrib/cvs/lib/system.h
./contrib/ncurses/ncurses/curses.priv.h
./contrib/ncurses/progs/progs.priv.h
./gnu/usr.bin/rcs/lib/rcsbase.h
./gnu/usr.bin/tar/pathmax.h
./include/limits.h
./lib/libc/gen/getcap.c
./crypto/heimdal/lib/roken/getcap.c
./crypto/kerberosIV/lib/roken/getcap.c

えーと次にすることはどんな風に使われてるのか、使われてないところは どんな風に処理してるのか、の検証、ですよね。時間できたらやります(……)

結論というか注意点というか

意味を明確に把握する

その定数がどういう意図で設定されているのかを理解せずに いい加減な使いかたをすると哀しいことになりそうです。 例えばMAX_PATHを「ファイル名の長さ」のMAXに使ってみたり。

また、たまたま望んでいた値に近いからといって、 その定数の適用範囲を超えた使用もよく考えたほうがよいでしょう。 stdio.hに宣言されている定数を、stdio以外の用途で使うとか。 いずれにしろ「意味を把握する」というところで総括できるかなと思います。

どこでも通用する不変の値ではない

すでに見てきたように、MAX_PATHやFILENAME_MAXの指す値は 環境に依存しますし、同一系列の環境でも今後変わらないという保証はありません。

コンパイル時に決定される

前項とかぶりますが...

これらは「定数」ですから値はコンパイル時に決定されます。 コンパイル時に決まる定数ということは、それをコンパイルした環境では 正しい値でも他のところに持っていって正しいとは限りません(コンパイル しなおせば別だが)。

守られていない定数もある

というと言いすぎかもしれませんが...

以上のようなことで、これらの定数は主にライブラリやAPIが 内部的に利用するものなのではないかと私は考えているのですが、 実際には、たとえばWindowsではMAX_PATHを超えた長さの パスを許すようなAPIが出ています。 FindFirstFileみたいな 関数とか。

ただでさえプラットホーム依存な上に、互換性問題とかで すでに定数の意味を果たせなくなっているものがあるように 思います。そういうものを信じて使っては泣いてしまうことになりそうです。

結論みたいな

以上の項を考えると以下のような結論が出ると思います。 なんというか、当たり前のことをわざわざ勿体つけて 書いてるような気もしますが。

永続化を目的とした定数として使うな

例えばファイルにパス名をセーブするときに、ファイル名が入る フィールド長をMAX_PATH固定長にする、といった使いかたはしないほうがよい でしょう。移植性がないばかりか、目の前ではきちんと動いているのに 他の場所でおかしいという最悪の部類のエラーを呼びこんで泣けます。 面倒でも可変長にするとかの手を考えましょう。

チェックを怠るな

FILENAME_MAXやMAX_PATHが最大長として信用できないことは 今まで述べてきた通りです。よって言うまでもないことですが bound checkを怠らないでください。これ以上の長さの 文字が来るということは、たとえそれがユーザからの 入力ではなくても、ありうることです。

他の定数との関係を見逃すな

MAX_???系の例を見て分かる通り(またMSDN Libraryにも 明記されている通り)、 絶対パスの最大長(_MAX_PATH)と、それを構成する要素(_MAX_DRIVE、 _MAX_EXT、_MAX_FNAME、_MAX_PATH)などの最大長の和とを 比べると後者の方が全然大きいです。 というわけで、これらの定数を使って絶対パスを合成するときは、 その長さが_MAX_PATHを超えないように注意しないと 前項の問題を自分から呼び込むことになります。

限界を超えるために

これらの定数は、

char filename[FILENAME_MAX];
みたいな使われかたをすることが多いようですが、 実際問題、それですべてが丸くおさまるわけではなさそうです。

つまり、

パス名の最大長を事前に知るポータブルな方法はない

ということが言えそうです。ここでいう「事前」とは コードを書いているその瞬間です。これを避けるためには といった手が考えられますが、前者は注意深く設計しないと MAX_PATHなどと同じ問題を繰り返すことになります。

後者の可変長の方ですが、Cだとmallocなどで確保したメモリを 誰の責任で解放するのかという問題があります。 誰もfreeしなければメモリリーク(の原因)になります。逆に二重にfreeして 落ちることもあるかもしれません。 (確保されたメモリが本当にmallocで確保されたものなのか、ということは 外からでは判断つきづらいという問題もあります)

可変長の文字列を渡すための方法としてよく見られるのは、

といった方法です。これならメモリの確保を利用者がすることになり 手間は増えますが管理は楽になるといえるかも。

具体的なやりかたとしては

みたいな流れになります。 もちろん、1回目と2回目の呼び出しで、戻ってくるべき「結果」が 同じであると分かってるときにしかこの手は使えないことは言うまでもありません。

↑で「NULLでもよい」「0でもよい」と書いたのは、NULLと0(の入った変数の アドレス)を渡して先に必要な長さを貰ってきてからメモリを用意するという手が 使えるという意味です。

感想

そもそもいつこういう定数が生まれたのか、どういう意図で生まれて どういう使われかたをしたのか、知らなきゃいけないことがやまもりです。

一生懸命調べればいろいろ勉強になるし、それを書けばきっと 楽しいドキュメントが書けると思ったが... なんかあれだ(どれや)


戻る
Zinnia (zinnia@risky-safety.org)