popen(“tar xvf tarball.tar”)适用于调试,但不适用于发布版本

时间:2014-09-01 20:57:34

标签: linux libcurl tar popen

我正在为Ubuntu开发一个C ++程序,它使用curl_easy_perform下载tar存档,在将存档下载到/ tmp之后,我使用popen执行相应的tar命令行。

当我运行程序的调试版本时,popen("tar xvf /tmp/example.tar -C /tmp/existingdir")有效,但是当我在发布版本中运行此命令时,popen调用总是失败。

这是我的代码,删除了大多数错误检查和不相关的内容:

//tl;dr version:
// first I download a tar archive from url using Curl and save it to filelocation,
// then I untar it using pOpen.  
// pOpen always works in debug, never in release builds
////
Status ExpandTarBall(const MyString& FileName)
{
    //extract the tar ball into a previously created temporary directory, tempDirPath
    MyString args = "tar xvf /tmp/ + FileName + " -C " + tempDirPath;
    cout << "running:" << args << endl;
    // args example:
    // tar xvf /tmp/UserIdXXxCtnAl/examplepackage -C /tmp/UserIdXXxCtnAl
    //
    Status result = ER_OPEN_FAILED;
    FILE* fp = popen(args.c_str(), "re");  //<==========  always works in debug builds, fails with 0 returned in release builds! :(
    if (fp)
    {
        result = pclose(fp) == 0 ? ER_OK : ER_INVALID_DATA;
    }

    return result;
}



    //Note: MyString is an std::string class with some local extensions
Status SslDownloader::DownloadFile(MyString url, MyString fileLocation, bool sslVerify) {

    CURL* curl = NULL;
    CurlInitHelper helper(curl);

    cout << "downloading from " << url.c_str() << " to " << fileLocation.c_str() << endl;

    if (!curl) {
        return ER_SSL_INIT;
    }
    FILE* fp = fopen(fileLocation.c_str(), "wb");
    if(NULL == fp) {
        return ER_OPEN_FAILED;
    }
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_USERAGENT, AJPM_USER_AGENT);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);

    if (sslVerify) {
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
        curl_easy_setopt(curl, CURLOPT_CAINFO, AJPM_CERT_STORE_LOCATION );
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    } else {
         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
    }

    CURLcode res = curl_easy_perform(curl);

    if (0 != fclose(fp)) {
        return ER_WRITE_ERROR;
    }

    if (res != CURLE_OK) {  
            return res == ER_SSL_CONNECT;
        }

    cout << "SSL download of " << fileLocation.c_str() << " succeeded\n";  // works every time
    return ExpandTarBall(const MyString& FileName);
}

我错过了什么简单的事情?

1 个答案:

答案 0 :(得分:2)

使用popen启动子进程后,立即调用pclose(),而不读取popen()返回的文件。

tar的xvf选项会在其标准输出上转储一个文件列表,这是一个管道,popen将管道的读取端返回给你。

pclose()首先关闭管道,然后等待子进程终止。

父进程和子进程同时运行,如果父进程赢得竞争并在子进程启动之前关闭管道,当子进程尝试写入其标准输出时,写入端管道,它将获得一个SIGPIPE信号,杀死子进程。

运行时配置文件在应用程序的“调试”和“发布”版本之间的差异很可能足以使规模向发布版本的结果倾斜,而实际上意味着任何实际意义上的额外开销。 “debug build”会使事情变慢,以便子进程有时间在父进程关闭管道之前泄漏其标准输出。

请记住,一旦你的tarball包含足够数量的文件,即使tar在这场比赛中领先,它的输出也会填满管道缓冲区并阻塞,一旦父进程转到关闭管道,它将SIGPIPE子进程,tar将始终失败。

故事的道德:当使用popen从已启动的子进程中读取时,总是从管道读取并使用子进程的输出,直到你得到一个EOF,然后才能pclose()它。

相关问题