おぎろぐはてブロ

なんだかんだエンジニアになって10年以上

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 が返されます。