4

tarballによって圧縮されるを生成するプログラムを作成しましたzlib
定期的に、同じプログラムがtarballに新しいファイルを追加することになっています。

定義によれば、tarballはempty records最後に正しく動作するために(512バイトブロック)必要です。これはすでに私の問題を示しています。

ドキュメントによると、モードでgzopenファイルを開くことがr+できません。つまり、空のレコードの先頭にジャンプしてファイル情報を追加し、空のレコードで再度封印することはできません。

今、私は機知に富んでいます。空のレコードが含まれていない限り、追加はzlibで正常に機能しますが、圧縮されたtarballを「ファイナライズ」するためにそれらが必要です。

何か案は?

ああ、そうです、全体を解凍したり、tarball全体を解析したりするのを避けることができればいいのですが。

また、tarの代わりに実装できる他の(できれば単純な)ファイル形式も利用できます。

4

2 に答える 2

3

これは2つの別々の問題であり、どちらも解決可能です。

1つ目は、tarファイルに追加する方法です。そこで行う必要があるのは、最後の2つのゼロ化された512バイトブロックをファイルで上書きすることだけです。512バイトのtarヘッダーを記述し、ファイルを整数の512バイトブロックに切り上げてから、ゼロで埋められた2つの512バイトブロックを書き込んで、tarファイルの新しい終わりをマークします。

2つ目は、gzipファイルに頻繁に追加する方法です。最も簡単なアプローチは、個別のgzipストリームを記述し、それらを連結することです。最後の2つの512バイトのゼロ化ブロックを別のgzipストリームに書き込み、それがどこから始まるかを覚えておいてください。次に、それを新しいtarエントリを含む新しいgzipストリームで上書きし、次に2つのエンドブロックを含む別のgzipストリームで上書きします。これは、でファイルを検索し、そこから書き込みを開始するためlseek()に使用することで実行できます。gzdopen()

これは、大きな(少なくとも数十Kの)追加ファイルに対しては、適切な圧縮でうまく機能します。ただし、非常に小さなファイルを追加する場合は、小さなgzipストリームを連結するだけでは、圧縮が悪くなり、さらに悪いことに拡張が発生します。圧縮アルゴリズムが相関と文字列照合のために先行するデータを利用できるように、実際に少量のデータを単一のgzipストリームに追加するために、より複雑なことを行うことができます。そのためには、zlibディストリビューションのgzlog.hgzlog.cのアプローチを見てください。examples/

簡単なアプローチの例を次に示します。

/* tapp.c -- Example of how to append to a tar.gz file with concatenated gzip
   streams. Placed in the public domain by Mark Adler, 16 Jan 2013. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include "zlib.h"

#define local static

/* Build an allocated string with the prefix string and the NULL-terminated
   sequence of words strings separated by spaces.  The caller should free the
   returned string when done with it. */
local char *build_cmd(char *prefix, char **words)
{
    size_t len;
    char **scan;
    char *str, *next;

    len = strlen(prefix) + 1;
    for (scan = words; *scan != NULL; scan++)
        len += strlen(*scan) + 1;
    str = malloc(len);                                  assert(str != NULL);
    next = stpcpy(str, prefix);
    for (scan = words; *scan != NULL; scan++) {
        *next++ = ' ';
        next = stpcpy(next, *scan);
    }
    return str;
}

/* Usage:

      tapp archive.tar.gz addthis.file andthisfile.too

   tapp will create a new archive.tar.gz file if it doesn't exist, or it will
   append the files to the existing archive.tar.gz.  tapp must have been used
   to create the archive in the first place.  If it did not, then tapp will
   exit with an error and leave the file unchanged.  Each use of tapp appends a
   new gzip stream whose compression cannot benefit from the files already in
   the archive.  As a result, tapp should not be used to append a small amount
   of data at a time, else the compression will be particularly poor.  Since
   this is just an instructive example, the error checking is done mostly with
   asserts.
 */
int main(int argc, char **argv)
{
    int tgz;
    off_t offset;
    char *cmd;
    FILE *pipe;
    gzFile gz;
    int page;
    size_t got;
    int ret;
    ssize_t raw;
    unsigned char buf[3][512];
    const unsigned char z1k[] =     /* gzip stream of 1024 zeros */
        {0x1f, 0x8b, 8, 0, 0, 0, 0, 0, 2, 3, 0x63, 0x60, 0x18, 5, 0xa3, 0x60,
         0x14, 0x8c, 0x54, 0, 0, 0x2e, 0xaf, 0xb5, 0xef, 0, 4, 0, 0};

    if (argc < 2)
        return 0;
    tgz = open(argv[1], O_RDWR | O_CREAT, 0644);        assert(tgz != -1);
    offset = lseek(tgz, 0, SEEK_END);                   assert(offset == 0 || offset >= (off_t)sizeof(z1k));
    if (offset) {
        if (argc == 2) {
            close(tgz);
            return 0;
        }
        offset = lseek(tgz, -sizeof(z1k), SEEK_END);    assert(offset != -1);
        raw = read(tgz, buf, sizeof(z1k));              assert(raw == sizeof(z1k));
        if (memcmp(buf, z1k, sizeof(z1k)) != 0) {
            close(tgz);
            fprintf(stderr, "tapp abort: %s was not created by tapp\n", argv[1]);
            return 1;
        }
        offset = lseek(tgz, -sizeof(z1k), SEEK_END);    assert(offset != -1);
    }
    if (argc > 2) {
        gz = gzdopen(tgz, "wb");                        assert(gz != NULL);
        cmd = build_cmd("tar cf - -b 1", argv + 2);
        pipe = popen(cmd, "r");                         assert(pipe != NULL);
        free(cmd);
        got = fread(buf, 1, 1024, pipe);                assert(got == 1024);
        page = 2;
        while ((got = fread(buf[page], 1, 512, pipe)) == 512) {
            if (++page == 3)
                page = 0;
            ret = gzwrite(gz, buf[page], 512);          assert(ret == 512);
        }                                               assert(got == 0);
        ret = pclose(pipe);                             assert(ret != -1);
        ret = gzclose(gz);                              assert(ret == Z_OK);
        tgz = open(argv[1], O_WRONLY | O_APPEND);       assert(tgz != -1);
    }
    raw = write(tgz, z1k, sizeof(z1k));                 assert(raw == sizeof(z1k));
    close(tgz);
    return 0;
}
于 2013-01-16T15:38:04.433 に答える
2

私の意見では、これはTARが標準に厳密に準拠している場合には不可能です。zlib[1]マニュアルとGNU tar[2]ファイルの仕様を読みました。TARへの追加を実装する方法についての情報は見つかりませんでした。したがって、空のブロックを上書きすることによって実行する必要があると想定しています。

ですから、繰り返しになりますが、を使用してそれを行うことができると思いますgzseek()sizeただし、非圧縮アーカイブ( )のサイズを知って、に設定する必要がありoffsetますsize-2*512。「whenceパラメータはlseek(2)のように定義されており、値SEEK_ENDはサポートされていない」ため、これは面倒な場合があることに注意してください。1であり、ファイルを同時に読み取りと書き込みのために開くことはできません。つまり、エンドブロックがどこにあるかを内省するためです。

ただし、TAR仕様をわずかに悪用する可能性があります。[ GNU tar2]ドキュメントは何か面白いことに言及しています:

「アーカイブされた各ファイルは、ファイルを説明するヘッダーブロックと、それに続くファイルの内容を示す0個以上のブロックで表されます。アーカイブファイルの最後には、末尾にバイナリゼロが入力された512バイトのブロックが2つあります。 -of-fileマーカー。妥当なシステムは、アーカイブの最後にそのようなファイルの終わりマーカーを書き込む必要がありますが、アーカイブを読み取るときにそのようなブロックが存在すると想定してはなりません。特に、GNU tarは、警告を発する場合は常に警告を発します。それに遭遇しないでください。」

つまり、これらのブロックを意図的に書き込むことはできません。tarballコンプレッサーを作成した場合、これは簡単です。次に、TARデコンプレッサが「壊れた」 TARファイルzlibを認識している必要があることを思い出して、通常の追加モードで使用できます。

[1] http://www.zlib.net/manual.html#Gzip [2] http://www.gnu.org/software/tar/manual/html_node/Standard.html#SEC182

于 2013-01-16T14:39:23.640 に答える