附加到gzipped Tar-Archive

时间:2013-01-16 12:57:38

标签: c++ compression append tar zlib

我编写了一个程序,生成tarball,由zlib压缩 应该定期向同一个程序添加一个新文件到tarball。

根据定义,tarball需要empty records(512字节块)才能正常工作,这已经显示了我的问题。

根据文档gzopen无法以r+模式打开文件,这意味着我不能简单地跳转到空记录的开头,附加我的文件信息并再次用空密封记录。

现在,我的智慧结束了。只要不涉及空记录,追加工作对zlib工作正常,但我需要它们来“完成”我的压缩tarball。

有什么想法吗?

啊,是的,如果我可以避免解压缩整个事情和/或解析整个tarball,那就太好了。

我也可以使用其他(最好是简单的)文件格式而不是tar。

2 个答案:

答案 0 :(得分:3)

这是两个独立的问题,两者都是可以解决的。

第一个是如何附加到tar文件。您需要做的就是用您的文件覆盖最后两个归零的512字节块。您可以编写512字节的tar标头,将文件四舍五入为整数个512字节的块,然后使用零填充两个512字节的块来标记tar文件的新结尾。

第二个是如何频繁附加到gzip文件。最简单的方法是编写单独的gzip流并将它们连接起来。将最后两个512字节的归零块写入单独的gzip流中,并记住它的起始位置。然后使用带有新tar条目的新gzip流覆盖它,然后使用两个结束块覆盖另一个gzip流。这可以通过使用lseek()回复文件,然后使用gzdopen()从那里开始撰写来完成。

对于增加的大文件(至少10 K的文件),这对于良好的压缩效果会很好。但是,如果你要添加非常小的文件,简单地连接小的gzip流将导致糟糕的压缩,或者更糟糕的是扩展。您可以执行更复杂的操作,以便将少量数据实际添加到单个gzip流中,以便压缩算法可以利用前面的数据进行关联和字符串匹配。为此,请查看gzlog.h分布中gzlog.cexamples/zlib的方法。

以下是如何进行简单方法的示例:

/* 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;
}

答案 1 :(得分:2)

在我看来,TAR严格遵守标准是不可能的。我仔细阅读了zlib [1]手册和GNU tar [2]文件规范。我没有找到任何有关如何实施附加到TAR的信息。所以我假设必须通过覆盖空块来完成。

所以我再次假设你可以使用gzseek()来做到这一点。但是,您需要知道未压缩存档(size)的大小,并将offset设置为size-2*512。 请注意,这可能很麻烦,因为“whence参数定义为lseek(2);不支持值SEEK_END。”1并且您无法同时打开文件进行读写,即对于结束块所在的内省。

然而,应该可以轻微滥用TAR规格。 GNU tar [2]文档提及搞笑

” 归档的每个文件由标题块表示,标题块描述文件,后跟零个或多个块,这些块给出文件的内容。在归档文件的末尾有两个512字节的块,用二进制零填充作为文件结束标记。合理的系统应该在归档的末尾写入这样的文件结束标记,但是在读取归档时不能假定存在这样的块。特别是GNU tar如果没有遇到它,总会发出警告。 “

这意味着,您可以故意不写这些块。如果编写tarball压缩器,这很容易。然后,您可以在正常附加模式下使用zlib,记住TAR解压缩程序必须知道“损坏的” TAR文件。

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