Как мне использовать Zlib с объединенными файлами .gz в WinAPI?

#c #winapi #gzip #zlib

#c #winapi #gzip #zlib

Вопрос:

Я загружаю обычные файлы обхода с AWS. По-видимому, это большие объединенные файлы .gz, которые поддерживаются стандартом gzip. Я использую zlib для дефляции, но я получаю только распакованное содержимое файла до первой конкатенации. Я попытался добавить inflateReset(), но затем я получаю ошибку -5, которая указывает на проблему с буфером или файлом. Я подозреваю, что я близок.

вот код без inflateReset. Он отлично работает с не объединенными файлами.

 #include "zlib.h"  
#define CHUNK 16384   
...
file = L"CC-MAIN-20181209185547-20181209211547-00040.warc.wet.gz";
fileDecompress(amp;file);

DWORD WINAPI fileDecompress(LPVOID lpParameter)
{
wstring dir = L"C:\AI\corpora\";
wstring* lpFileName = static_cast<wstring*>(lpParameter);
sendToReportWindow(L"File to decompress is "%s" in "%s"n", lpFileName->c_str(), dir.c_str());
wstring sourcePath = dir   lpFileName->c_str();
sendToReportWindow(L"input file with path:%sn", sourcePath.c_str());
wstring destPath = dir   lpFileName->c_str()   L".wet";
sendToReportWindow(L"output file with path:%sn", destPath.c_str());

HANDLE InputFile = INVALID_HANDLE_VALUE;
HANDLE OutputFile = INVALID_HANDLE_VALUE;
BOOL Success;
DWORD InputFileSize;
ULONGLONG StartTime, EndTime;
LARGE_INTEGER FileSize;

//  Open input file for reading, existing file only.
InputFile = CreateFile(
    sourcePath.c_str(),       //  Input file name, compressed file
    GENERIC_READ,             //  Open for reading
    FILE_SHARE_READ,          //  Share for read
    NULL,                     //  Default security
    OPEN_EXISTING,            //  Existing file only
    FILE_ATTRIBUTE_NORMAL,    //  Normal file
    NULL);                    //  No template

if (InputFile == INVALID_HANDLE_VALUE)
{
    sendToReportWindow(L"Cannot open input t%sn", sourcePath.c_str());
    return 0;
}

OutputFile = CreateFile(
    destPath.c_str(),         //  Input file name, compressed file
    GENERIC_WRITE,            //  Open for reading
    0,                        //  Share for read
    NULL,                     //  Default security
    CREATE_ALWAYS,            //  Existing file only
    FILE_ATTRIBUTE_NORMAL,    //  Normal file
    NULL);                    //  No template

if (OutputFile == INVALID_HANDLE_VALUE)
{
    sendToReportWindow(L"Cannot open output t%sn", destPath.c_str());
    return 0;
}

//  Get compressed file size.
Success = GetFileSizeEx(InputFile, amp;FileSize);
if ((!Success) || (FileSize.QuadPart > 0xFFFFFFFF))
{
    sendToReportWindow(L"Cannot get input file size or file is larger than 4GB.n");
    CloseHandle(InputFile);
    return 0;
}
InputFileSize = FileSize.LowPart;

sendToReportWindow(L"input file size: %u bytesn", InputFileSize);

int ret;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];

strm.zalloc = Z_NULL;              // allocate inflate state
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;

ret = inflateInit2(amp;strm, 16   MAX_WBITS);
if (ret != Z_OK)
{
return 0;
}

do {                                                                    /* decompress until deflate stream ends or end of file */  
    DWORD read;
    BOOL res = ReadFile(InputFile, in, CHUNK, amp;read, NULL);

    strm.avail_in = read;
    if (!res) {
        (void)inflateEnd(amp;strm);
        sendToReportWindow(L"read error on input filen");
        return 0;
    }

    if (strm.avail_in == 0)
    {
        break;
    }
    strm.next_in = in;


        /* run inflate() on input until output buffer not full */
    do {
        strm.avail_out = CHUNK;
        strm.next_out = out;
        ret = inflate(amp;strm, Z_NO_FLUSH);

        assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
        switch (ret) {
        case Z_NEED_DICT:                                           // 2
            sendToReportWindow(L"z_need_dict:%dn", ret);
            (void)inflateEnd(amp;strm);
            return 0;
            //ret = Z_DATA_ERROR;     /* and fall through */
        case Z_DATA_ERROR:                                          // -3
            sendToReportWindow(L"z_data_error:%dn", ret);
            (void)inflateEnd(amp;strm);
            return 0;
        case Z_MEM_ERROR:                                           // -4
            (void)inflateEnd(amp;strm);
            sendToReportWindow(L"z_mem_error:%dn", ret);
            sendToReportWindow(L"ret:%dn", ret);
            DisplayErrorBox((LPWSTR)L"inflate");
            return 0;
        case Z_BUF_ERROR:                                           // -5
            sendToReportWindow(L"z_buf_error:%dn", ret);
            (void)inflateEnd(amp;strm);
            return 0;
        }

        have = CHUNK - strm.avail_out;   
        DWORD written;
        BOOL res = WriteFile(OutputFile, out, have, amp;written, NULL);

        if (written != have || !res) {
            (void)inflateEnd(amp;strm);
            sendToReportWindow(L"file write error:%dn", res);
            return 0;
        }
 
    } while (strm.avail_out == 0);          //  avail_out == 0 means output buffer is full 
} while (ret != Z_STREAM_END);  /* done when inflate() says it's done */            // Z_STREAM_END is 1

(void)inflateEnd(amp;strm);
CloseHandle(InputFile); CloseHandle(OutputFile);
return 0;
}
  

Вот версия с добавленным inflateReset() . эта версия приводит к тому, что inflate генерирует ошибку -5 (плохой буфер или усеченный файл).

 ...
int ret;
z_stream strm{};
array<uint8_t, CHUNK> scratch = {}; //scratch buffer for decompressing the data.

strm.zalloc = Z_NULL;              // allocate inflate state
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;

ret = inflateInit2(amp;strm, 16   MAX_WBITS);
if (ret != Z_OK)
{
    return 0;
}

do {                                                                    /* decompress until deflate stream ends or end of file */ 
    DWORD read;
    BOOL res = ReadFile(InputFile, in, CHUNK, amp;read, NULL);

    strm.avail_in = read;
    if (!res) {
        (void)inflateEnd(amp;strm);
        sendToReportWindow(L"read error on input filen");
        return 0;
    }

    if (strm.avail_in == 0)
    {
        sendToReportWindow(L"strm.avail_in:%dn", strm.avail_in);       // strm.avail_in = 0
        break;
    }
    strm.next_in = in;

        /* run inflate() on input until output buffer not full */
    do {
        strm.avail_out = scratch.size();
        strm.next_out = scratch.data();
        ret = inflate(amp;strm, Z_NO_FLUSH);

        //if (ret != Z_OK) break;                                     // 0
        
        switch (ret) {
        case Z_NEED_DICT:                                           // 2
            sendToReportWindow(L"z_need_dict:%dn", ret);
            (void)inflateEnd(amp;strm);
            return 0;
            //ret = Z_DATA_ERROR;     /* and fall through */
        case Z_STREAM_ERROR:                                        // -2
            sendToReportWindow(L"Z_STREAM_ERROR:%dn", ret);
            (void)inflateEnd(amp;strm);
            return 0;
        case Z_DATA_ERROR:                                          // -3
            sendToReportWindow(L"z_data_error:%dn", ret);
            (void)inflateEnd(amp;strm);
            return 0;
        case Z_MEM_ERROR:                                           // -4
            (void)inflateEnd(amp;strm);
            sendToReportWindow(L"z_mem_error:%dn", ret);
            sendToReportWindow(L"ret:%dn", ret);
            DisplayErrorBox((LPWSTR)L"inflate");
            return 0;
        case Z_BUF_ERROR:                                           // -5
            sendToReportWindow(L"z_buf_error:%dn", ret);
            (void)inflateEnd(amp;strm);
            //return 0;
            break;
        }

        auto bytes_decoded = scratch.size() - strm.avail_out;
       
        DWORD written;
        BOOL res = WriteFile(OutputFile, amp;scratch, bytes_decoded, amp;written, NULL);

        if (ret == Z_STREAM_END) break;

    } while (true);          //  avail_out == 0 means output buffer is full

    ret == Z_STREAM_END;

    auto reset_result = inflateReset(amp;strm);        // work with concatenation
    sendToReportWindow(L"resetting inflate: %dn", reset_result);
    assert(reset_result == Z_OK);      

} while (strm.avail_in > 0);
...
  

Спасибо!

обновление: я думаю, что ReadFile должен считываться в ЧАНКЕ вместо 1. изменено для обоих примеров. Теперь это выдает ошибку -3: «Z_DATA_ERROR». проверка, чтобы увидеть, действительно ли это изменение теперь слишком часто попадает в readfile.

типичный файл, который я хочу удалить: [https://commoncrawl.s3.amazonaws.com/crawl-data/CC-MAIN-2018-51/segments/1544376823009.19/wet/CC-MAIN-20181209185547-20181209211547-00041.warc.wet.gz]

обновление 2: Спасибо, Марк Адлер! используя приведенный вами пример, я смог исправить логику в своем коде. это удовлетворяет требованиям WinAPI. Я также добавил обработку внешних файлов, переместил вещи в кучу и добавил таймер. Таймер показал, что увеличение объема памяти помогло сократить время выкачивания на 30%.

 DWORD WINAPI fileDecompress(LPVOID lpParameter)
{                                                                                
// zlib does not work with .zip files
sendToReportWindow(L"inside fileDecompress()n");                            
// deflate .gz (gzip) files. single or multiple member (concatenated)

wstring dir = L"C:\AI\corpora\";
wstring* lpFileName = static_cast<wstring*>(lpParameter);
sendToReportWindow(L"File to decompress is "%s" in "%s"n", lpFileName->c_str(), dir.c_str());
wstring sourcePath = dir   lpFileName->c_str();
sendToReportWindow(L"input file with path:%sn", sourcePath.c_str());

wstring::size_type lastdot = lpFileName->find_last_of(L".");                 // remove .gz extension: get length to last dot and truncate
lpFileName->resize(lastdot);
wstring destPath = dir   lpFileName->c_str();
sendToReportWindow(L"output file with path:%sn", destPath.c_str());

HANDLE InputFile = INVALID_HANDLE_VALUE;
HANDLE OutputFile = INVALID_HANDLE_VALUE;
BOOL Success;
DWORD InputFileSize;
ULONGLONG StartTime, EndTime;
LARGE_INTEGER FileSize;
double InflateTime;

InputFile = CreateFile(
    sourcePath.c_str(),       //  Input file name, compressed file
    GENERIC_READ,             //  Open for reading
    FILE_SHARE_READ,          //  Share for read
    NULL,                     //  Default security
    OPEN_EXISTING,            //  Existing file only
    FILE_ATTRIBUTE_NORMAL,    //  Normal file
    NULL);                    //  No template

if (InputFile == INVALID_HANDLE_VALUE){sendToReportWindow(L"Cannot open input t%sn", sourcePath.c_str()); return 0; }

OutputFile = CreateFile(
    destPath.c_str(),         //  Input file name, compressed file
    GENERIC_WRITE,            //  Open for reading
    0,                        //  Share for read
    NULL,                     //  Default security
    CREATE_ALWAYS,            //  Existing file only
    FILE_ATTRIBUTE_NORMAL,    //  Normal file
    NULL);                    //  No template

if (OutputFile == INVALID_HANDLE_VALUE){sendToReportWindow(L"Cannot open output t%sn", destPath.c_str()); return 0; }

Success = GetFileSizeEx(InputFile, amp;FileSize);                              // Get compressed file size.
if ((!Success) || (FileSize.QuadPart > 0xFFFFFFFF))
{
    sendToReportWindow(L"Cannot get input file size or file is larger than 4GB.n");
    CloseHandle(InputFile);
    return 0;
}
InputFileSize = FileSize.LowPart;
sendToReportWindow(L"input file size: %u bytesn", InputFileSize);

StartTime = GetTickCount64();

#define CHUNK 524288                                                        // buffer size. doesn't use much ram and speeds up inflate
z_stream strm = {};                                                         // Initialize zlib for file compression/decompression
int ret = inflateInit2(amp;strm, 16   MAX_WBITS);
assert(ret == Z_OK);

unsigned char *in = new unsigned char[CHUNK]; unsigned char* out = new unsigned char[CHUNK];   

for (;;) {                                                                  // Decompress from input to output.
    if (strm.avail_in == 0) {                                               // Keep reading until the end of the input file or an error
        DWORD read;
        (void)ReadFile(InputFile, in, CHUNK, amp;read, NULL);
        strm.avail_in = read;
        if (strm.avail_in == 0)
            break;
        strm.next_in = in;
    }

    do {                                                                    // Decompress all of what's in the CHUNK in buffer.
        strm.avail_out = CHUNK;                                                     
        strm.next_out = out;
        ret = inflate(amp;strm, Z_NO_FLUSH);                                   // Decompress as much as possible to the CHUNK out buffer.
                                                                          
        size_t got = CHUNK - strm.avail_out;                                
        DWORD written;                                                      
        (void)WriteFile(OutputFile, out, got, amp;written, NULL);              // Write to the outputFile whatever inflate() left in out buffer
        if (written != got) {sendToReportWindow(L"file write errorn"); delete[] in; delete[] out; return 0;}
                                                                                                                      
        if (ret == Z_STREAM_END)                                            // Check for the end of a gzip member, in which case, 
            assert(inflateReset(amp;strm) == Z_OK);                            // reset inflate for the next gzip member. (concatenated files)

        else if (ret != Z_OK) {                                             // Return on a data error.
            assert(ret == Z_DATA_ERROR);
            (void)inflateEnd(amp;strm);
            delete[] in; delete[] out;
            return 0;
        }   
    } while (strm.avail_in > 0);                                            // Continue until everything in the input buffer is consumed.
}                                                                           // for() loop to get next input buffer CHUNK from input file    

EndTime = GetTickCount64();
InflateTime = (EndTime - StartTime) / 1000.0;                               //  Get how long it took to inflate file

delete[] in; delete[] out;
(void)inflateEnd(amp;strm);                                                       
CloseHandle(InputFile); CloseHandle(OutputFile);
sendToReportWindow(L"Inflate Time: %.2f seconds. Done with fileDecompress function.n", InflateTime);
return 0;
}
  

Ответ №1:

Ваш компилятор, по крайней мере, не предупреждает вас о голом условном ret == Z_STREAM_END; обозначении? Вам нужен if там и несколько фигурных скобок вокруг inflateReset() связанных операторов.

Все еще существует проблема в том, что вы покидаете внешний цикл, если strm.avail_in значение равно нулю. Это будет происходить каждый раз, кроме момента достижения конца элемента. Это может произойти даже тогда, если вы просто случайно исчерпаете входной буфер для распаковки этого элемента. Просто сделайте внешний цикл a while (true) .

Даже после исправления всего этого вы бы отбросили оставшийся доступный ввод при выполнении чтения в верхней части внешнего цикла. Это чтение выполняется только в том случае, если strm.avail_in оно равно нулю.

Более простым подходом было бы выполнить сброс во внутреннем цикле. Вот так (пример на C):

 // Decompress a gzip file input, potentially with multiple gzip members. Write
// the decompressed data to output. Return Z_STREAM_END on success. Return Z_OK
// if the gzip stream was correct up to where it ended prematurely. Return
// Z_DATA error if the gzip stream is invalid.
int inflate_gzip(FILE *input, FILE *output) {
    // Initialize inflate for gzip input.
    z_stream strm = {};
    int ret = inflateInit2(amp;strm, 16   MAX_WBITS);
    assert(ret == Z_OK);

    // Decompress from input to output.
    unsigned char in[CHUNK];
    for (;;) {
        // Keep reading until the end of the input file or an error.
        if (strm.avail_in == 0) {
            strm.avail_in = fread(in, 1, CHUNK, input);
            if (strm.avail_in == 0)
                break;
            strm.next_in = in;
        }

        // Decompress all of what's in the input buffer.
        do {
            // Decompress as much as possible to the CHUNK output buffer.
            unsigned char out[CHUNK];
            strm.avail_out = CHUNK;
            strm.next_out = out;
            ret = inflate(amp;strm, Z_NO_FLUSH);

            // Write to the output file whatever inflate() left in the output
            // buffer. Return with an error if the write does not complete.
            size_t got = CHUNK - strm.avail_out;
            size_t put = fwrite(out, 1, got, output);
            if (put != got)
                return Z_ERRNO;

            // Check for the end of a gzip member, in which case reset inflate
            // for the next gzip member.
            if (ret == Z_STREAM_END)
                assert(inflateReset(amp;strm) == Z_OK);

            // Return on a data error.
            else if (ret != Z_OK) {
                assert(ret == Z_DATA_ERROR);
                (void)inflateEnd(amp;strm);
                return ret;
            }

            // Continue until everything in the input buffer is consumed.
        } while (strm.avail_in > 0);
    }

    // Successfully decompressed all of the input file. Clean up and return.
    assert(inflateEnd(amp;strm) == Z_OK);
    return ret;
}
  

Комментарии:

1. Я этого не вижу. когда я просматриваю программу (отладчик vs 2019), он вызывает readfile один раз, получает кучу «iiiiiiiiiiiiiii», а затем запускает deflate, который возвращает 0 (ok). но когда дело доходит до bytes_decoded, оно равно нулю, потому что scratch.size и strm.avail.out равны 1024. поэтому он никогда не перебирает файл записи. затем он выполняет резервное копирование, но выдает ошибку -5 при inflate, которая сбрасывает оба цикла.

2. Я думаю, что ReadFile должен считываться в ЧАНКЕ вместо 1. изменено для обоих примеров. Теперь это выдает ошибку -3: «Z_DATA_ERROR»

3. внутренний цикл завершается, поскольку обнаружен z_stream_end. затем вызывается inflateReset, и внешний цикл продолжается вверху. но ошибка-3 может указывать на то, что «поток был освобожден преждевременно (некоторые входные или выходные данные были удалены)». запутался…

4. Обновленный ответ с несколькими обнаруженными ошибками. Ключевым моментом является то, что вы перезаписываете входной буфер в верхней части цикла, когда у вас все еще есть неиспользуемый ввод.