即使我告诉 libzip 不要释放 libzip zip_source_t 缓冲区

libzip zip_source_t buffer is freed even tho I told libzip not to

我遇到了 libzip 问题,我不知道是不是我做错了什么,是不是库中的错误。

首先,我用 zip_source_buffer_create() 从磁盘上的 zip 文件创建一个存档(不要问我为什么不直接打开文件)。

我将 freep 标志设置为 0,这应该意味着我将处理释放自己的缓冲区,这不是 lipzip 的工作。

如果我不向存档中添加文件,它会起作用。

如果我将文件添加到存档中,当我删除缓冲区时,libzip 已经释放了它,我的应用程序崩溃了。这是库中的错误吗?

我写了这个不言自明的 mcve。使用 AUTO_FREE_BUF=0ADD_FILE=1,应用程序崩溃。每隔 (3) 个案例都有效。

libzip 版本:1.7.3

#include <zip.h>
#include <string>
#include <filesystem>
#include <fstream>
#include <iostream>

#define AUTO_FREE_BUF 0 //1-0
#define ADD_FILE 1 //1-0

int main(int argc, char** argv) {

    const std::string add_file = "file.txt";
    const std::string zip_file = "zipfile.zip";

    size_t sz = std::filesystem::file_size(zip_file);
    char* buf = new char[sz];
    {
        std::ifstream ifs{ zip_file, std::ios::binary };
        ifs.read(buf, sz);
        ifs.close();

        zip_error_t ze;
        zip_error_init(&ze);
        zip_source_t* zs = zip_source_buffer_create(buf, sz, AUTO_FREE_BUF, &ze);
        if (zs == nullptr) {
            zip_error_fini(&ze);
            return -1;
        }

        zip_error_init(&ze);
        zip_t* z = zip_open_from_source(zs, NULL, &ze);
        if (z == nullptr) {
            zip_error_fini(&ze);
            return -1;
        }
#if ADD_FILE == 1
        //add file
        {
            zip_source_t* file = zip_source_file(z, add_file.c_str(), 0, -1);
            if (file == nullptr)
                return -1;

            zip_int64_t add = zip_file_add(z, add_file.c_str(), file, ZIP_FL_ENC_GUESS);
            if (add == -1)
                return -1;
        }
#endif
        zip_error_fini(&ze);
        zip_source_keep(zs);
        int close = zip_close(z);
        if (close == -1)
            return -1;

        //write back archive to disk
        //..

#if AUTO_FREE_BUF == 1
        zip_source_free(zs);
#else
        zip_source_free(zs); //<-is supposed to NOT free the buffer (with freep=0) but sometimes does
        delete[] buf;
#endif
    }

    return 0;
}

经过(现在几个小时)的调查,我想我理解了这背后的基本原理,我认为 libzip 没有正常运行。

zip_source_free(src) 减少 src 的引用计数,如果它达到 0,则 src 对象被释放。此外,如果 src 是使用 freep=1 创建的,则关联的数据缓冲区也会被释放。如果 freep=0 则不应释放数据缓冲区。

但在这种情况下和某些情况下,缓冲区是免费的,这可能是无意的。

在上面的 mcve 中,将文件添加到存档中会使 libzip 释放数据缓冲区,即使源是使用 freep=0 创建的。如果没有文件添加到存档中,则缓冲区不会按预期释放,我可以安全地 delete[] 它。

这不是我的问题的解决方案。添加文件后,我在 mcve 中注意到 src.zip_err=18 [Invalid argument],因此文件的添加方式可能有问题 - 我还没有发现它有什么问题。

我已经从源代码安装了 libzip(如 install.md 中所述)并修改了您的源代码。我添加了一个文本文件(带有“Hello world”)和一个包含一个文本文件的 zip 文件。

我没有收到错误。 (但是,zip 文件中没有添加任何内容,所以这也不对)

(忽略被注释掉的异常参数 :P )

#include <zip.h>

#include <exception>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>

static constexpr auto add_file{"file.txt"};
static constexpr auto zip_file{"zipfile.zip"};
static constexpr auto AUTO_FREE_BUF{false};  //1-0
static constexpr auto ADD_FILE{true};        //1-0

class MsgException : public std::exception {
   public:
    MsgException(std::string message) : _message(message) {}
    char const *what() const noexcept override { return _message.c_str(); }

   private:
    std::string const _message;
};

int main() {
    std::cout << "Started main\n";
    auto const sz = std::filesystem::file_size(zip_file);
    auto *const buf = new char[sz];
    {
        std::ifstream ifs{zip_file, std::ios::binary};
        ifs.read(buf, sz);
    }
    std::cout << "Zipfile read\n";

    zip_error_t ze;
    try {
        zip_error_init(&ze);
        auto *zs = zip_source_buffer_create(buf, sz, AUTO_FREE_BUF ? 1 : 0, &ze);
        if (zs == nullptr) throw MsgException("Can't create source: %s." /*, zip_error_strerror(&ze)*/);

        //zip_error_init(&ze);
        auto *z = zip_open_from_source(zs, 0, &ze);
        if (z == nullptr) throw MsgException("Can't open zip from source: %s." /*, zip_error_strerror(&ze)*/);

        if (ADD_FILE) {
            //add file
            auto *file = zip_source_file(z, add_file, 0, -1);
            if (file == nullptr) throw MsgException("Can't create data source from file '%s': %s." /*, add_file, zip_strerror(z)*/);

            if (zip_file_add(z, add_file, file, ZIP_FL_ENC_GUESS) < 0) {
                //? zip_source_free(file);
                throw MsgException("Can't file to zip archive: %s." /*, zip_strerror(z)*/);
            }
        }

        zip_source_keep(zs);

        if (zip_close(z) < 0) throw MsgException("Can't close zip archive: %s." /*, zip_strerror(z)*/);

        //write back archive to disk
        //..

        zip_source_free(zs);  //<-is supposed to NOT free the buffer (with freep=0) but sometimes does
    } catch (std::exception &e) {
        std::cerr << "Program aborted with error: " << e.what() << '\n';
    }
    zip_error_fini(&ze);
    if (!AUTO_FREE_BUF) delete[] buf;

    std::cout << "Finished main\n";
    return 0;
}

构建使用

g++ -std=c++17 main.cpp -lzip -o main

编辑:添加了一些 RAII

#include <zip.h>

#include <exception>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

static constexpr auto add_file{ "file.txt" };
static constexpr auto zip_file{ "zipfile.zip" };
static constexpr auto AUTO_FREE_BUF{ false };  //1-0
static constexpr auto ADD_FILE{ true };        //1-0

class MsgException : public std::exception {
public:
    MsgException(std::string message) : _message(message) {}
    char const* what() const noexcept override { return _message.c_str(); }

private:
    std::string const _message;
};

//RAII
class ZipError {
public:
    ZipError() { zip_error_init(&ze); }
    ~ZipError() { zip_error_fini(&ze); }
    zip_error* operator&() { return &ze; }
    // actually probably need to delete copy/move constructor/assignment etc...
private:
    zip_error_t ze;
};

int main() {
    std::cout << "Started main\n";
    std::vector<char> buf(std::filesystem::file_size(zip_file)); // RAII
    {
        std::ifstream ifs{ zip_file, std::ios::binary };
        ifs.read(buf.data(), buf.size());
    }
    std::cout << "Zipfile read\n";

    
    try {
        ZipError ze;
        auto* zs = zip_source_buffer_create(buf.data(), buf.size(), AUTO_FREE_BUF ? 1 : 0, &ze);
        if (zs == nullptr) throw MsgException("Can't create source: %s." /*, zip_error_strerror(&ze)*/);

        //zip_error_init(&ze);
        auto* z = zip_open_from_source(zs, 0, &ze);
        if (z == nullptr) throw MsgException("Can't open zip from source: %s." /*, zip_error_strerror(&ze)*/);

        if (ADD_FILE) {
            //add file
            auto* file = zip_source_file(z, add_file, 0, -1);
            if (file == nullptr) throw MsgException("Can't create data source from file '%s': %s." /*, add_file, zip_strerror(z)*/);

            if (zip_file_add(z, add_file, file, ZIP_FL_ENC_GUESS) < 0) {
                //? zip_source_free(file);
                throw MsgException("Can't file to zip archive: %s." /*, zip_strerror(z)*/);
            }
        }

        zip_source_keep(zs);

        if (zip_close(z) < 0) throw MsgException("Can't close zip archive: %s." /*, zip_strerror(z)*/);

        //write back archive to disk
        //..

        zip_source_free(zs);  //<-is supposed to NOT free the buffer (with freep=0) but sometimes does
    }
    catch (std::exception& e) {
        std::cerr << "Program aborted with error: " << e.what() << '\n';
    }
    
    std::cout << "Finished main\n";
    return 0;
}

不要使用 libzip 处理使用 WinRar 创建的 zip 压缩文件......(使用 libzip 创建它们)