26行のANSI Cコードのtarを読み取ります

アーカイバ-怖い! 普通の人が彼の人生で決して理解できないであろう巨大で恐ろしいアルゴリズム! Rar、zip、gzip、tarは現代の事実上の標準です。つまり、理解しようとすべきではない、非常に複雑で洗練されたものです。 さて、tarはもっとシンプルに見えますが、おそらくすべてがそれほど複雑ではありませんか? ソースコードでgitを探します。 数十キロバイトの数十のファイルがあります。 うーん どうやら行き止まり。







__________________| |____________________________________________ ,--. ,--. ,--. ,--. |oo | _ \ `. | oo | | oo| oo|~~ |(_) / ; | ~~ | | ~~|ooooooooooo |/\/\| '._,' |/\/\| |/\/\| __________________ ____________________________________________ | |dwb
      
      





実際、すべてがそれほど複雑ではありません。 ドキュメントでは、tarは複数のファイルをテープに書き込む方法にすぎないと説明されていました。 つまり すべてがシンプルでなければなりません。 実際には、各ファイルとそのコンテンツのサポート情報のセットが直接。 26行でtarファイルのリーダーを作成できるようになったのは、この事実の理解でした。







zipが完全に支配されているときにtarが必要になるのはなぜですか? 私にとって、tarを使用するという問題は、ミニチュアCアプリケーションで景品アーカイバを取得したいときに発生します。つまり、 実行可能ファイルの成長を最小限に抑え、不要な依存関係なし。 たとえば、 dxPmdxConverterユーティリティは、BMPを読み取り、 LodePNGを使用してPNGに変換できます。 つまり アプリケーションには、圧縮されたPNG形式でピクセルの配列を「アーカイブ」する機能が既にあります。 また、PNGはzipおよびgzipで使用されるDeflateアルゴリズムによって圧縮されます。 さらに、gzipでは直接使用されます。gzipヘッダーが書き込まれ、Deflateからのデータストリーム、crc-sumです。 また、出力は完成した.gzファイルであり、任意のアーカイバーで開くことができます。 ただし、gzipは1つのファイルのみを圧縮できます。 つまり 圧縮する前に、いくつかのファイルを何らかの形で1つに結合する必要があります。 最も一般的な方法はtarです。







  ____ _ _ ____ __ ____ __ _ _ __ ____ ______ | _ \| \ | |/ ___| / / | _ \ ___ / _| | __ _| |_ ___ / / / ___|__ (_)_ __ | |_) | \| | | _ / / | | | |/ _ \ |_| |/ _` | __/ _ \ / / | | _ / /| | '_ \ | __/| |\ | |_| | / / | |_| | __/ _| | (_| | || __/ / / | |_| |/ /_| | |_) | |_| |_| \_|\____| /_/ |____/ \___|_| |_|\__,_|\__\___| /_/ \____/____|_| .__/ |_|
      
      





次回、同様の状況でtarが役に立ちました。 Wordlaseゲームのリソースをオープン形式で保存しないために、それらをアーカイブすることが決定されました。 はい、開発者のマシンに好きなだけ詰め込めます。 ただし、ゲームの開始ごとにリソースが展開されます。 つまり ソリューションは高速に動作するはずです。 圧縮アルゴリズムのパブリックドメイン実装はインターネットで見つかりましたが、1つのファイルのみをパックする方法も知っています。 そのため、この出版物のヒーローは生まれました-dxTarRead







dxTarReadの利点:









主な欠点は、tarファイルを最初にメモリに完全に読み込む必要があることです。 一方、リソースはとにかく使用されます。 ロードされます。 次に、すぐにディスクからロードして、必要に応じてtarデータを使用してアレイからそれらを取得します。







タール 標準に関する基本情報は、 GNU.orgにあります。 基本的に、「struct posix_header」構造体の説明だけが必要でした。 そこから定数が取得されます:







  const int NAME_OFFSET = 0, SIZE_OFFSET = 124, MAGIC_OFFSET = 257; const int BLOCK_SIZE = 512, NAME_SIZE = 100, SZ_SIZE = 12, MAGIC_SIZE = 5;
      
      





実際、これらの定数は次のように読み取ることができます。tarブロックの先頭から124バイト(SIZE_OFFSET)にシフトすると、次の12バイト(SZ_SIZE)でtar内にファイルサイズが格納されます。







ドキュメントをさらに読むことを忘れないでください。 そこでは、サイズが8進数で保存されていることがわかります;-)すなわち SZ_SIZEの最後からバイトを読み取り、8を掛けると、通常の10進数形式でサイズが取得されます。







上記をCで書き留めると、次のようになります。







 const char* sz = tar + SIZE_OFFSET + currentBlockStart; long size = 0; for(i=SZ_SIZE-2, mul=1; i>=0; mul*=8, i--) /* Octal str to int */ if( (sz[i]>='1') && (sz[i] <= '9') ) size += (sz[i] - '0') * mul;
      
      





tarブロックのトピックに近づきました。 それはたった512バイトのデータです。tarヘッダー、または直接ファイルに書き込まれた行のいずれかです。 ファイルの最後のブロックが512バイト未満の場合、512バイトは予約されたままです。 つまり 各tarballは次のようになります。







 +-------+-------+-------+-------+-------+-------+ | tar 1 | file1 | ... | file1 | tar 2 | file2 | ... +-------+-------+-------+-------+-------+-------+
      
      





保存されたファイルのサイズを示すヘッダーtarを持つブロックがあります。 次に、ファイルの内容を含むNブロックがあります。 つまり tarの次のファイルに移動するには、(N + 1)* 512バイトにシフトする必要があります。 コード:







 newOffset = (1 + size/BLOCK_SIZE) * BLOCK_SIZE; /* trim by block size */ if( (size % BLOCK_SIZE) > 0 ) newOffset += BLOCK_SIZE;
      
      





アルゴリズムは次のとおりです。







  1. ブロックからファイル名とサイズを読み取ります。
  2. ファイル名が探しているものと一致する場合、リンクをユーザーに返します。
  3. それ以外の場合は、次のブロックにジャンプして手順1から繰り返します。


ファイル名を比較するには、ループにstrncmpアナログを実装する必要がありました。







 i = 0; while((i<NAME_SIZE) && (fileName[i]!=0) && (name[i]==fileName[i])) i++; if( (i > 0) && (name[i] == 0) && (fileName[i] == 0) ) found = 1;
      
      





それだけです すべてのソースコードのレビュー。 全機能コード:







 const char* dxTarRead(const void* tarData, const long tarSize, const char* fileName, long* fileSize) { const int NAME_OFFSET = 0, SIZE_OFFSET = 124, MAGIC_OFFSET = 257; const int BLOCK_SIZE = 512, NAME_SIZE = 100, SZ_SIZE = 12, MAGIC_SIZE = 5; const char MAGIC[] = "ustar"; /* Modern GNU tar's magic const */ const char* tar = (const char*) tarData; /* From "void*" to "char*" */ long size, mul, i, p = 0, found = 0, newOffset = 0; *fileSize = 0; /* will be zero if TAR wrong or there is no such file */ do { /* "Load" data from tar - just point to passed memory*/ const char* name = tar + NAME_OFFSET + p + newOffset; const char* sz = tar + SIZE_OFFSET + p + newOffset; /* size string */ p += newOffset; /* pointer to current file's data in TAR */ for(i=0; i<MAGIC_SIZE; i++) /* Check for supported TAR version */ if( tar[i + MAGIC_OFFSET + p] != MAGIC[i] ) return 0; /* = NULL */ size = 0; /* Convert file size from string into integer */ for(i=SZ_SIZE-2, mul=1; i>=0; mul*=8, i--) /* Octal str to int */ if( (sz[i]>='1') && (sz[i] <= '9') ) size += (sz[i] - '0') * mul; /* Offset size in bytes. Depends on file size and TAR's block size */ newOffset = (1 + size/BLOCK_SIZE) * BLOCK_SIZE; /* trim by block */ if( (size % BLOCK_SIZE) > 0 ) newOffset += BLOCK_SIZE; i = 0; /* strncmp - compare file's name with that a user wants */ while((i<NAME_SIZE) && (fileName[i]!=0) && (name[i]==fileName[i])) i++; if( (i > 0) && (name[i] == 0) && (fileName[i] == 0) ) found = 1; } while( !found && (p + newOffset + BLOCK_SIZE <= tarSize) ); if( found ){ *fileSize = size; return tar + p + BLOCK_SIZE; /* skip header, point to data */ } else return 0; /* No file found in TAR - return NULL */ }
      
      





おわりに



tarはデータを圧縮しませんが、クリアテキストで保存します。 これにより、新しいメモリを割り当てるのではなく、単に既存のメモリへのポインタを返すことができます。







tarブロックのサイズは512バイトです。 さらに、各ファイルにtarヘッダーを保存する必要があります。 つまり サイズが数バイトのファイルは、tarアーカイブで1キロバイトを占有します。 たくさんの小さなファイルを保存する必要があり、それでもファイルを圧縮しない場合、tarは悪い選択です。







GitHubでのdxTarRead







PSハブ「私はPRです!」 見ました。 そして、「仕事を探している」ハブはどこですか? :)








All Articles