WindowsのPHPは大きなファイルを扱えない?

WindowsのPHPは大きなファイルを扱えない?

- Tatsuya Shirai の投稿
返信数: 7

 現在,WindowsXP SP3上で,PHP5.2.0を利用しています.

 何気なくbackupdataフォルダの中を見てみたところ,ファイルサイズの表示が添付ファイルのような状態になっていました.マイナス! ちなみにbackupdataフォルダ自体のサイズは2GBと表示されました.2GB + 2GB + 2GB - 2GB - 2GB + 108バイトで2GBなのでしょうか...

 ファイルシステムにNTFSを使用していますので2GBだろうと4GBだろうと問題無いのですが,多分,これはWindows版PHPが2GBを越えるファイルに対応できていないということでしょうか...一応,ZIPファイルが作成され,それがコピーできたということだとは思うのですが...filesize()の戻り値がおかしいだけだと祈ります.


以下のようなPHPプログラムを作成して動作確認を行ったところ,

function display_size($size) {
    static $gb, $mb, $kb, $b;
    if (empty($gb)) {
        $gb = 'GB';
        $mb = 'MB';
        $kb = 'KB';
        $b  = 'byte';
    }
    if ($size >= 1073741824) {
        $size = round($size / 1073741824 * 10) / 10 . $gb;
    } else if ($size >= 1048576) {
        $size = round($size / 1048576 * 10) / 10 . $mb;
    } else if ($size >= 1024) {
        $size = round($size / 1024 * 10) / 10 . $kb;
    } else {
        $size = $size .' '. $b;
    }
    return $size;
}

$size = 4;
for ($i = 1; $i <= 5; $i++) {
    echo $size.'-> '.display_size($size).'<br/>';
    $size *= 1024;
}

出力結果は,

4-> 4 byte
4096-> 4KB
4194304-> 4MB
4294967296-> 4GB
4.3980465111E+012-> 4096GB

ご覧の通りに正しく表示されますので,整数値がオーバーフローしている訳では無さそうです.少なくとも4294967296が整数値として取り扱い可能な訳ですから.

 そろそろPHP5.2.0からPHP5.2.6へ変える潮時でしょうか.

添付 filesize_windows.jpg
Tatsuya Shirai への返信

Re: WindowsのPHPは大きなファイルを扱えない?

- Tatsuya Shirai の投稿

http://php.benscom.com/manual/ja/function.filesize.php

 2GBを越えるファイルのfile_size()が正しく返されない問題は,どうやらWindows版PHPだけでは無さそうですね.fs_moodleでは,OSがWindowsの場合はlib/fs_moodle/fs_override.php中の,Linux/MacOSではlib/fs_moodle/fs_no_override.php中のfs_file_size()を介してfile_size()にアクセスしています.そこで上記PHPマニュアルにもあるように,sprintf("%u", file_size())とすることで,この問題が回避可能であることを確認しました.

 本来ですと,本家のMoodleで対応して貰えると助かるのですけれども...

Tatsuya Shirai への返信

2GBを越えるファイル(zip)のダウンロードが正常に行なえない

- Tatsuya Shirai の投稿

 先のfilesize()関数に関する問題と関連があるのかも知れませんし,Windows (fs_moodle)だけの問題なのかLinuxでも発生するのかも分かりません.

 「2GBを越えるファイルをアップロードすることはない」

と思うかも知れませんが,当方のサイトのコースの中にはバックアップファイル(backupdata)のサイズが2GBを越えるものがあります.現象は,

  • Moodle上でzipファイルのリストが表示できない/解凍できない
  • ダウンロードするとファイルサイズが変わる
  • ダウンロードしたzipファイルのリストがおかしい(添付ファイル参照)

です.zip書庫に含まれるファイルのリストに関する情報はzipファイルの末尾に格納されています.ファイルサイズが2GB程度になっていることから,fread()関数が誤動作したのではないかと予想されます.1MBずつfread()で読んではechoしていますので,読み出し箇所が2GBに達したところでそこから先が読めなくなったのでしょうか.

 zip書庫に含まれていないmoodle/libやmoodle/modといったフォルダ構造がzip書庫に紛れ込むのは怪奇現象としか言いようが無いのですが...なんだろう.以前にも同じことがあって,でも忙しかったので気のせいと言うことにしたのは甘かった.

http://www.phppro.jp/news/75

以前にfilesize()について調べた際に上記ページも参照したのですが,これを読んだときにある程度,予想しておくべきでした.

添付 2GBover.jpg
Tatsuya Shirai への返信

Re: 2GBを越えるファイル(zip)のダウンロードが正常に行なえない

- Tatsuya Shirai の投稿

 まずはMoodle上でzipファイルをリスト表示(書庫内のファイル名のリスト)が表示できない問題あたりから取り掛かろうと考えて手を付けてみました.結論から言うと「普通の方法では無理そう」です.

 lib/pclzip/pclzip.lib.phpの中にもいくつかfilesize()があり,実際にそれが"PCLZIP_ERR_BAR_FORMAT(-10)の原因の一つでした.そこに対策(とはいえ4GBまでしか対応できない方法)を施して見たところ,また別の箇所がエラー発生要因となっていました.ファイルポインタをファイルの末尾まで移動させるのに,privReadEndCentralDir()の中で,@fseek($this->zip_fd, $v_size)を実行します.$v_sizeはファイルサイズであり,オリジナルでは負の値をとってしまうのを以前に示したsprintf()を使う方法で辛うじて正しい値を得ています.そしてそのファイルポインタの位置をftell($this->zip_fd)で取得し,$v_sizeと一致するかをチェックするのですが,これが一致しない.fseek()の戻り値は0なので一応,コマンドとしてはエラーを出していないが,ftell()をすると末尾ではなく2GBちょっきりあたりを指し示している.そして一致しないからエラー.

 fseek()にしても,filesize()にしても,引数や戻り値の整数値は符号付きlongであり,32bit OSでデフォルトでは2GBを越えられない.64bitの指示をつけてコンパイルする必要があるらしいのですが,環境に思いっきり依存してしまいますね.

 この問題(2GBを越えるzip書庫をMoodle上でリスト表示/解凍できない)はこの辺りで手を引きます.Moodleとは別次元の話です.せめてリスト表示くらいはしたかったのですけれども.

 ダウンロードできない問題は気がかりですね.MPEG2のファイルをやり取りするようなことを考えるのであれば注意が必要です.


 もしfilesize()を調べて負のサイズを返す場合は別の手段でファイル操作をする,といったクレイジーな方法も一瞬だけ頭を過ぎりました.たとえば操作対象のファイルを(安全をみて)1GB程度に分割して...ファイルを指定したサイズに分割するコマンドをWindowsとLinux/MacOS用(OSが持っているかな,後者は)に用意して,境目を越えたら該当するファイルを開いて,といった感じです.異常ですね.

#この辺がPHPの限界でしょうか.

 それともphp.iniに何か設定を指定するとintが64bitになり,fseek()やfilesize()が64bitのintに対応するといった良い話を耳にした方はいないでしょうか.未来の話でも結構です.(改造版のPHPではなく,正式なPHP)

 取り合えず再コンパイルの場合は以下参照:

http://www.tymy.net/~matsu/blog/2008/11/05/php%E3%81%A72gb%E4%BB%A5%E4%B8%8A%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%89%B1%E3%81%86/

Tatsuya Shirai への返信

Re: 2GBを越えるファイル(zip)のダウンロードが正常に行なえない

- Tatsuya Shirai の投稿

 バックアップファイルが2GBを越えたのは,当方の場合,コースファイルに大きなファイルを多数含んでいたためです.したがって,そのようなコースをコピーする際には,コースファイルは手でコピーし,バックアップ&リストア時にはコースファイルを含めないようにすればお引越しはできるでしょう.

 なんらかの問題で,バックアップ済みのzip書庫を使ってコースをリストアしたい場合,どうやらzip書庫の作成自体はおかしくなさそうなので,course filesのフォルダ内を削除してリストアし,course files内のファイルは手で書き戻す,これでなんとかなりそうです.

#moddataフォルダの中身が巨大な場合も同じ手でいけるかどうかは自信がありません.(course filesの場合についても確認した訳ではありません)

Tatsuya Shirai への返信

PHPは大きなファイルを扱えない?

- Tatsuya Shirai の投稿

 現状のPHPが2GBを越えるファイルを正常に取り扱えない問題については,外部コマンドを使ってcopy(cp)するなどの手段を講じる必要があるでしょう.一方,以外に面倒なのがファイルサイズを取得するfilesize()で,これをそのまま代替する外部コマンドがありません.

 とりあえずちょっとテストしてみて良好だった手法(Linux用)はこちらのページにあるようにstatコマンドを使うものでした.4GBを越えるファイルでも正常にファイルサイズを取得できました(正確か,は未確認).fs_moodleではfilesize()関数の代替コマンドとしてfs_filesize()という関数を用意しています.これを以下のように修正してテストしました(くどいですが,Linuxで,です).

    function fs_filesize($fname)
    {
        $size = exec('stat -c %s "'.$fname.'"');
        return intval($size);
//      return sprintf("%u", filesize($fname));
    }

コメントアウトした元のコードは,2GBから4GBの間のファイルについては正常に動作しますが,4GBを越えるファイルに対しては,たとえば51MBといった誤ったファイルサイズを返します.追加した2行が外部コマンドを使ったコードです.参照元のコードでは$fnameの両端をダブルクオーテーションで括っていませんので,ファイル名に半角空白文字を含むと正常に動作しません.

 Windowsでも同様のコマンドがあると良いのですが...exec()でdirコマンドの結果を受け取った後に文字列処理を行わないといけないですねぇ.

なお,デバッグを行うには望んだサイズのファイルを作成する必要があります.
Linux: DDコマンドを利用する方法
Windows: fsutilコマンドを利用する方法