我正在为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);
}
我错过了什么简单的事情?
答案 0 :(得分:2)
使用popen启动子进程后,立即调用pclose(),而不读取popen()返回的文件。
tar的xvf选项会在其标准输出上转储一个文件列表,这是一个管道,popen将管道的读取端返回给你。
pclose()首先关闭管道,然后等待子进程终止。
父进程和子进程同时运行,如果父进程赢得竞争并在子进程启动之前关闭管道,当子进程尝试写入其标准输出时,写入端管道,它将获得一个SIGPIPE信号,杀死子进程。
运行时配置文件在应用程序的“调试”和“发布”版本之间的差异很可能足以使规模向发布版本的结果倾斜,而实际上意味着任何实际意义上的额外开销。 “debug build”会使事情变慢,以便子进程有时间在父进程关闭管道之前泄漏其标准输出。
请记住,一旦你的tarball包含足够数量的文件,即使tar在这场比赛中领先,它的输出也会填满管道缓冲区并阻塞,一旦父进程转到关闭管道,它将SIGPIPE子进程,tar将始终失败。
故事的道德:当使用popen从已启动的子进程中读取时,总是从管道读取并使用子进程的输出,直到你得到一个EOF,然后才能pclose()它。