Автор Тема: Странный глюк Lua + zlib  (Прочитано 2980 раз)

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Странный глюк Lua + zlib
« : Март 10, 2013, 02:21:54 pm »
Понадобилось мне тут распаковывать данные из deflate.

Сначала взял бинд к zlib тут: https://code.google.com/p/lua-files/source/browse/zlib.lua
Поправил инициализацию, т.к. у меня данные без заголовка и контрольной суммы:
local function init_inflate(finally)
    local strm = ffi.new'z_stream'
    check(C.inflateInit2_(strm, -15, M.version(), ffi.sizeof(strm)))
    finally(function() check(C.inflateEnd(strm)) end)
    return strm, inflate
end
Все отлично работает, но мне захотелось избавиться от зависимостей, которые тянет этот бинд. Кроме того он слишком сложно написан для меня. И из функционала мне нужна только функция inflate().

В общем взял из этого бинда только zlib_h.lua (привязка через ffi) и переписал на lua функцию int inf(FILE *source, FILE *dest) из этого примера: http://www.zlib.net/zpipe.c
local function inflate(source)
    local ssize = #source
    local spos = 0
    local function read(start, len)
        spos = math.min(ssize, start + len)
        return source:sub(start + 1, spos), spos - start
    end

    local dest = {}
    local have = 0
    local strm = ffi.new 'z_stream'
    local out  = ffi.new('uint8_t[?]', CHUNK)

    -- allocate inflate state
    strm.zalloc = nil;
    strm.zfree  = nil;
    strm.opaque = nil;
    strm.avail_in, strm.next_in = 0, nil;

    local ret = zlib.inflateInit2_(strm, -15, zlib_version, ffi.sizeof(strm));
    if ret ~=  zlib.Z_OK then return ret end

    -- decompress until deflate stream ends or end of file
    repeat
        strm.next_in, strm.avail_in = read(spos, CHUNK)
        if strm.avail_in == 0 then break end
       
        -- run inflate() on input until output buffer not full
        repeat
            strm.avail_out = CHUNK;
            strm.next_out = out;
            ret = zlib.inflate(strm, zlib.Z_NO_FLUSH)
            assert(ret ~= zlib.Z_STREAM_ERROR); -- state not clobbered
            if ret == zlib.Z_NEED_DICT then
                ret = zlib.Z_DATA_ERROR -- and fall through
            -- elseif zlib.Z_DATA_ERROR then
            elseif ret == zlib.Z_MEM_ERROR then
                zlib.inflateEnd(strm)
                return ret
            end
            have = CHUNK - strm.avail_out;
            if have > 0 then
                dest[#dest+1] = ffi.string(out, have)
            end
        until strm.avail_out ~= 0
    -- done when inflate() says it's done
    until ret == zlib.Z_STREAM_END

    -- clean up and return
    zlib.inflateEnd(strm)
    return ret == zlib.Z_STREAM_END and zlib.Z_OK or zlib.Z_DATA_ERROR, table.concat(dest)
end
Принимает и возвращает строку.

И оно работает, но если я распаковываю в цикле много файлов (около 16000), то моя реализация выдает пару-тройку битых файлов (всегда разных). Т.е. они распаковываются, zlib ошибок не выдает, но результат кривой. Если использую бинд из lua-files, то никаких глюков нет.

Кусок кода, вызывающий inflate(), выглядит так:
RowBody = ReadRowBody(rd)
    if RowBody:sub(1, 3) ~= BOM then
        local ret, res = inflate(RowBody)
        if ret == zlib.Z_OK then
            if res:sub(1, 3) == BOM then
                local file = assert(io.open("c:\\temp\\test2\\"..rd.pos()..".txt", "wb"))
                file:write(res:sub(4))
                file:close()
            end
        else
            print(zerr[ret])
        end
    end

Где я накололся? O_o

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Странный глюк Lua + zlib
« Ответ #1 : Март 10, 2013, 05:50:43 pm »
Блин когда же я внимательно читать научусь...: https://code.google.com/p/lua-files/source/browse/zlib.lua#61

Вот правильная реализация:
local function inflate(source)
    local ssize = #source
    local spos = 0
    local function read(start, len)
        spos = math.min(ssize, start + len)
        return source:sub(start + 1, spos), spos - start
    end

    local dest = {}
    local have = 0
    local strm = ffi.new 'z_stream'
    local out  = ffi.new('uint8_t[?]', CHUNK)

    -- allocate inflate state
    strm.zalloc = nil;
    strm.zfree  = nil;
    strm.opaque = nil;
    strm.avail_in, strm.next_in = 0, nil;

    local ret = zlib.inflateInit2_(strm, -15, zlib_version, ffi.sizeof(strm));
    if ret ~=  zlib.Z_OK then return ret end

    local data, size -- data must be anchored as an upvalue!!!
   
    -- decompress until deflate stream ends or end of file
    repeat
        data, size = read(spos, CHUNK)
        strm.next_in, strm.avail_in = data, size
        if strm.avail_in == 0 then break end

        -- run inflate() on input until output buffer not full
        repeat
            strm.avail_out = CHUNK;
            strm.next_out = out;
            ret = zlib.inflate(strm, zlib.Z_NO_FLUSH)
            assert(ret ~= zlib.Z_STREAM_ERROR); -- state not clobbered
            if ret == zlib.Z_NEED_DICT then
                ret = zlib.Z_DATA_ERROR -- and fall through
            -- elseif zlib.Z_DATA_ERROR then
            elseif ret == zlib.Z_MEM_ERROR then
                zlib.inflateEnd(strm)
                return ret
            end
            have = CHUNK - strm.avail_out;
            if have > 0 then
                dest[#dest+1] = ffi.string(out, have)
            end
        until strm.avail_out ~= 0
    -- done when inflate() says it's done
    until ret == zlib.Z_STREAM_END

    -- clean up and return
    zlib.inflateEnd(strm)
    return ret == zlib.Z_STREAM_END and zlib.Z_OK or zlib.Z_DATA_ERROR, table.concat(dest)
end

ps Теперь три восклицательных знака  ;D