PHPを使用したtarおよびgzアーカイブの操作

これが頻繁に起こるように、それはすべて、phpを使用してtar.gzアーカイブを処理できるものが必要だったという事実から始まりました。 インターネットで調べてみると、このトピックに関して何も受け入れられるものがなかったことに驚きました。



何がありますか?



1. PHPのPEAR拡張http://pear.php.net/package/Archive_Tar Fineですが、サーバー設定にアクセスできないため、私の場合は受け入れられません。 強制的にスイープ。

2. Alexei Valeevによる素晴らしい記事「phpでtar.gzアーカイブを操作する」 。 必要なものですが、悲しいかな。 私は、質問を提起できない「透明な」ライセンスのソリューションが必要でした。 そのため、Bitrixのライブラリの使用も良くありませんでした。



実際、それだけです。



検索エンジンをさらに選別しても、妥当なものは得られませんでした。 少し考えてから、人気のあるnet2ftpのコードにクロールしました。これは、思い出すように、tarアーカイブを完全に処理します。 2001年にVincent Blavetからpcltar.lib.phpライブラリがあることが判明しました。 GNUライセンス すべてがそうであるように。 しかし! まず、ライブラリ自体のサイズが127キロバイトであることに混乱しました。 まあ、私は昔からbzikを持っています-私はまだバイトを数えます。 次に、結果を個別の関数としてではなく、クラスとして取得したかったのです。 さらに、興奮が跳ね上がりました。 徹底的に把握したかった。



その結果、tarアーカイブ構造の説明( ここでは情報ブロックのタイトルブロックがよく描かれています )を見つけて、コードを調べる必要がありました。 以下に結果を示します。 タスクが特定のものであることは理解していますが、おそらく誰かが役に立つでしょう。



したがって、ご存知のように、現代の意味でのtarアーカイバはそうではありません。 テープメディアにデータを保存するように設計されているため、圧縮方法はわかりませんが、多くのファイルを1つにまとめ、独自のヘッダーを追加し、結果のコードを512バイトの偶数ブロックに追加します。 その後、アーカイバによって結果を既に圧縮できます。 どっち? はい、少なくともrar。 違いはありません。 ただし、伝統的に、これにはgzipおよびbzip2形式が使用されます。 彼らはただ2つのファイルをリンクすることはできないので(これはUnixシステムで採用されている「1つのプログラム-1つのアクション」ポリシーによって決定されます)。 PHPでのgzipおよびbzip2のサポートはサードパーティのライブラリによって提供されており、当社にとって重要ではありません。 タール自体は重要です。



ファイル構造を簡単に分析します。 予想どおり、見出しが最初になります。 ドキュメントを調べた結果、「古い」ヘッダー形式と「新しい」ヘッダー形式があることがわかりました。 新規-512バイト長。 「古い」フィールドに追加のフィールドを追加することで実現しました。 理論的には互換性がありますが、現在に焦点を当てます。 分解してみましょう。 ここで、要するに、要点:



100バイトの名前-名前(相対パスが含まれる場合があります);

8バイトモードファイルモード

8バイトuid-ユーザーID

8バイトgid-グループID

12バイトサイズ-ファイルサイズ、バイト(8進数でエンコード)

12バイトmtime-UNIX時代の秒単位の最終変更日時(8進数でエンコード)

8バイトのchksum-ヘッダーのチェックサム(ファイルではありません!)

1バイトのtypeflag-ファイルを定義します、またはディレクトリ:file-0、directory-5

100バイトのリンク名-ファイルへのリンク

-さらに-「新しい」形式のフィールド-6バイトの魔法-「ustar」という単語が含まれます。 「新しい」形式のサイン

2バイトバージョン-新しい形式のバージョン(存在しない場合があります)

32バイトuname-所有者名

32バイトのgname-所有者グループ名

8バイトdevmajor-デバイスコードの上位バイト

8バイトdevminor-デバイスコードの下位バイト

155バイトのプレフィックス-名前のプレフィックス(拡張子)



コード「20」(スペース)は許可されますが、未使用のバイトは空でなければなりません。



通常、このデータのほとんどは必要ありません。 個人的には、名前、サイズ、日付に興味がありました。



次に、情報部分自体が追加されます(注意!)最大512バイトの倍数までの空のバイトが追加されます。 そして、次のファイルのためにもう一度。 ご覧のとおり、すべてがシンプルです。



実際、この知識はファイルを圧縮するのに十分です。



1. fopenコマンド(file_name)でアーカイブを開きます。



2.タイトル。 これが問題の最も難しい部分です。 前述のライブラリpcltar.lib.phpの関数を使用して、自転車を発明しませんでしたが、わずかに最適化しました。 バルクのため、ここですべてのコードを提供するわけではありませんが、本質は次のアクションです。

-ファイルの名前、サイズ、作成日、ファイルに設定されている権限を決定します。 ディレクトリの場合、ゼロのサイズを指定します。

-未使用のパラメーターは空として宣言されます。

-数値パラメーター(サイズ、日付)を8進数に変換します。

-対応するフィールドの宣言されたサイズに従って、各パラメーターをフォーマットします。 ここには、すぐに理解できなかったトリックが1つあります。実際、各フィールドの大部分は、フィールド自体のサイズよりも1バイト小さくなければなりません。 最後のバイトは空でなければなりません。 そうしないと、アーカイブを読み取ることができません。

-すべてのパラメーターを2行にまとめます。 2つ。それらの間にヘッダーチェックサムが必要だからです。

-このチェックサムを考慮し、同じルールに従ってフォーマットし、パックします。

-そして今、アーカイブファイルに3行書き込みます:パラメーターの最初の部分、チェックサム、パラメーターの2番目の部分。



できた! 作成時間の例を次に示します。



$mtime = sprintf("%11s ", DecOct(filemtime($filename)));



pack("a100a8a8a8a12a12", …, …, …, …, …, $mtime);








3.ファイル本体を使用することですべてがシンプルになり、ライブラリ内のVincent Blavetもpack関数で処理します。 しかし、私はさまざまなファイルでいくつかの実験を行ったが、パック/アンパックする際に歪みは見られなかった。 したがって、生産性を得るために、私はそれをしませんでした-それは意味がありません。 もちろん、ファイルからデータを読み取ります-最初に開いた後、アーカイブに書き込みます。 私の場合のファイルサイズは非常に大きくなる可能性があるため、ブロック単位でこれを行います。 50 Kbで使用したブロックサイズ。



$infile = fopen($filename, rb);

$j = ceil(filesize($filename) / 51200) + 1;

for($i=0; $i<$j; $i++){

$fr = fread($infile, 51200);

if ($this->tarmode == "tar")

@fputs($this->tarfile, $fr);

else

@gzputs($this->tarfile, $fr);

}

fclose($infile);








4.そして今、私たちは「偶数」に終わります。 これを行うには、「十分ではなかった」バイト数を知る必要があります。 ファイルが512バイト未満の場合、これは512からサイズを減算して決定されます。それより大きい場合、ファイルサイズを512で除算した余りを決定し、512から減算します。結果はバイナリ文字列にパックされます。



また、ファイルが最初に512バイトの倍数である場合も考慮する必要があります。一部のプログラムは、ファイルを必要なサイズに個別に補完します。 もちろん、この場合、追加する必要はありません。



結果のコードは次のとおりです。



$ffs = filesize($filename);

if($ffs > 512)

$tolast = 512 - fmod($ffs, 512);

else

$tolast = 512-$ffs;

if($tolast != 512 && $tolast != 0){

$fdata = pack("a".$tolast, "");

.

}








結果はtarアーカイブです。 これで、次のファイルで操作を繰り返すか、アーカイブを閉じることができます。

Zlibライブラリが接続されている場合、アーカイブを作成するプロセスで、アーカイブを圧縮して「tar.gz」または「tgz」を好きなように取得できます。 ライブラリをチェックする最も簡単な方法は、FORCE_GZIP定数をチェックすることです。 プロセスを自動化するために、アーカイブファイルを使用したすべての操作に対してこのようなチェックを導入しました。 このようなもの:



if(defined('FORCE_GZIP'))

$resopen = @fopen($this->tarname, 'a+b');

else

$resopen = @gzopen($this->tarname, 'a+b'.$this->tarlevel);








したがって、実際には、将来のファイル拡張子を決定し、すでにそれに焦点を合わせて、必要な機能を使用していますが、これは重要ではありません。



残りの操作ははるかに簡単です。 アーカイブからファイルを削除したり、ファイルを検索するなどの機能は必要なかったため、上で書いたようにZlibライブラリの自動検出のみをクラスに追加し、ファイルのリストを取得してそれらを解凍しました。 この記事の執筆中に、アーカイブを完全に解凍するための別の機能を追加することになりました。



すべてのヘッダーを見つけて読み取ることで、アーカイブ内のファイルのリストを取得できます。 これを行うには、アーカイブの最初の512バイトを読み取ります。これはヘッダーであり、unpack()関数でアンパックします。 unpackは連想配列に解凍されるため、同時にパラメーターに明確な名前を割り当てます。 このように:



unpack("a100name/a8perms/… …", “ ”)







作成時間とサイズを10進数に戻す必要があります。



取得したパラメーターは「出力に」送信できます。 アーカイブファイル内のポインタをパックファイルの読み取りサイズにシフトし、残りを512バイトブロックにシフトするためだけに残ります。 これで、次の見出しの先頭をポイントし、操作を再度繰り返すことができます。



目的のファイルのアンパックは、前の関数を使用してヘッダーを検索し、指定された場所に指定された名前でファイルを作成し、アーカイブ内のファイルポインターをファイルコンテンツの先頭に移動し、その長さに対応するバイト数を読み取り、作成されたファイルに書き込むことになります。 ディレクトリの場合、すべてが作成に制限されます。



ここでの2つの困難は、Zlibライブラリの機能に関連しています。



まず第一に。 このライブラリのgzopen関数では、fopen関数と同様に、書き込みと読み取りのために同時にファイルを開くための「+」修飾子は実装されていませんでした。 アーカイブファイルの1回のオープン/クローズを放棄し、タスクに従って、呼び出しごとにこれらの操作を繰り返す必要がありました。



第二に、ドキュメンテーションには、fseekと同様のgzseek関数が「エミュレートされていますが、動作が非常に遅い」と書かれています(そして、この表示の信ver性に確信がありました)。 アーカイブファイル内のポインターを目的の位置に直接シフトすることを放棄し、それを「空の」読み取り値に置き換えて、パフォーマンスを低下させる必要がありました。 物事がtarアーカイブに限定されていれば、これは回避できたはずです。



実際、それがすべてです。 その結果、11 Kbの非圧縮コードのサイズの、完全にユニバーサルなライブラリを手に入れました。 ライブラリはArchivator_tar-tar_gz.zipからダウンロードできます。



いつもあなたのもの、PunkerPoock



All Articles