PHP5.2.5からのglob()関数の動作変更
最近知った事実ということで、メモ。
glob関数でマッチしないときは、空配列が返る。 (但し、プラットフォーム依存)
php -dopen_basedir= -r 'var_dump(glob("/non/exsistent/path"));' array(0) { }
なんですが、
open_basedir に何かしら設定されている場合、もしくは safe_mode が on のときは 5.2.5 から false が返るようになっています。(引数の検索パスが * など含まず実在のパスを指している場合は従来通り空配列が返却されます)
php -dopen_basedir=/tmp -r 'var_dump(glob("/non/exsistent/path"));' bool(false)
glob自体の実行が失敗してfalseなのか、それともマッチが0件なのか分からないという素敵な仕様です。safe_mode云々のWarningメッセージも出力されません。
globが失敗することは、あんまり無いと思いますが、PHPでちゃんとマッチが0件かを調べるのには、dir() か DirectoryIterator でファイル一覧を走査して、自前でパターンマッチさせないといけないようです。
何が変わった?
この修正で入った、具体的にはここらへんでこの挙動の変化が起きています。
Fixed regression in glob() when enforcing safe_mode/open_basedir checks on paths containing '*'
ということで、パス中に * が含まれていたときに safe_mode と open_basedir のチェックを強制するというものです。(チェックというにはあれな実装だけど)
コード的には以下の部分で、
if (0 != (ret = glob(pattern, flags & GLOB_FLAGMASK, NULL, &globbuf))) { #ifdef GLOB_NOMATCH if (GLOB_NOMATCH == ret) { /* Some glob implementation simply return no data if no matches were found, others return the GLOB_NOMATCH error code. We don't want to treat GLOB_NOMATCH as an error condition so that PHP glob() behaves the same on both types of implementations and so that 'foreach (glob() as ...' can be used for simple glob() calls without further error checking. */ goto no_results; } #endif RETURN_FALSE; } /* now catch the FreeBSD style of "no matches" */ if (!globbuf.gl_pathc || !globbuf.gl_pathv) { no_results: if (PG(safe_mode) || (PG(open_basedir) && *PG(open_basedir))) { struct stat s; if (0 != VCWD_STAT(pattern, &s) || S_IFDIR != (s.st_mode & S_IFMT)) { RETURN_FALSE; } } array_init(return_value); return; }
要はマッチが0件だったときに、
// safe_modeが有効もしくは、open_basedirが設定されているときに if (PG(safe_mode) || (PG(open_basedir) && *PG(open_basedir))) { struct stat s; // globの引数のパスが存在しないパスの場合 (*とか入ってると当然存在しないパス)、 // もしくは存在して且つそれがディレクトリであるときに if (0 != VCWD_STAT(pattern, &s) || S_IFDIR != (s.st_mode & S_IFMT)) { // false を返す RETURN_FALSE; } }
という条件文で結果 false が返されます。