IQA-463: Use APR file I/O for downloaded viewer installer .exe.
On Windows, calling CreateProcess(bInheritHandles=FALSE) is the wrong idea. In that case, CreateProcess() passes NO handles -- even the files you've explicitly designated as the child's stdin, stdout, stderr in the STARTUPINFO struct! Remove LLProcess code to tweak bInheritHandles; we should also remove the corresponding (useless) APR extension. Instead, given that the Windows file-locking problem we've observed is specific to the viewer installer .exe file downloaded by the background updater logic, use APR file I/O for that specific file. Empirically, both llofstream and std::ofstream seem to make the open file handle inheritable; but apr_file_open() documentation says: "By default, the returned file descriptor will not be inherited by child processes created by apr_proc_create()." And indeed, it does appear to sidestep the locking problem.master
parent
eb1bea2223
commit
78816bb156
|
|
@ -282,9 +282,13 @@ public:
|
|||
|
||||
virtual std::string read(size_type len)
|
||||
{
|
||||
// Read specified number of bytes into a buffer. Make a buffer big
|
||||
// enough.
|
||||
// Read specified number of bytes into a buffer.
|
||||
size_type readlen((std::min)(size(), len));
|
||||
// Formally, &buffer[0] is invalid for a vector of size() 0. Exit
|
||||
// early in that situation.
|
||||
if (! readlen)
|
||||
return "";
|
||||
// Make a buffer big enough.
|
||||
std::vector<char> buffer(readlen);
|
||||
mStream.read(&buffer[0], readlen);
|
||||
// Since we've already clamped 'readlen', we can think of no reason
|
||||
|
|
@ -535,24 +539,6 @@ LLProcess::LLProcess(const LLSDOrParams& params):
|
|||
apr_procattr_t *procattr = NULL;
|
||||
chkapr(apr_procattr_create(&procattr, gAPRPoolp));
|
||||
|
||||
#if ! defined(APR_HAS_PROCATTR_INHERIT_SET)
|
||||
// Our special preprocessor symbol isn't even defined -- wrong APR
|
||||
LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_inherit_set() extension" << LL_ENDL;
|
||||
#elif ! APR_HAS_PROCATTR_INHERIT_SET
|
||||
// Symbol is defined, but to 0: expect apr_procattr_inherit_set() to
|
||||
// return APR_ENOTIMPL.
|
||||
LL_DEBUGS("LLProcess") << "apr_procattr_inherit_set() not supported on this platform" << LL_ENDL;
|
||||
#else // APR_HAS_PROCATTR_INHERIT_SET nonzero
|
||||
// As of 2012-04-17, the original Windows implementation of
|
||||
// apr_proc_create() unconditionally passes TRUE for bInheritHandles. That
|
||||
// seems to assume that all files are opened by APR so you can
|
||||
// individually control whether each is inherited by a child process. But
|
||||
// we've been burned by having surprising open file handles inherited by
|
||||
// our child processes. Turn that OFF for us!
|
||||
LL_DEBUGS("LLProcess") << "Setting apr_procattr_inherit_set(0)" << LL_ENDL;
|
||||
ll_apr_warn_status(apr_procattr_inherit_set(procattr, 0));
|
||||
#endif
|
||||
|
||||
// For which of stdin, stdout, stderr should we create a pipe to the
|
||||
// child? In the viewer, there are only a couple viable
|
||||
// apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
/**
|
||||
/**
|
||||
* @file llupdatedownloader.cpp
|
||||
*
|
||||
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2010, Linden Research, Inc.
|
||||
*
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
|
@ -40,6 +40,7 @@
|
|||
#include "llthread.h"
|
||||
#include "llupdaterservice.h"
|
||||
#include "llcurl.h"
|
||||
#include "llapr.h"
|
||||
|
||||
class LLUpdateDownloader::Implementation:
|
||||
public LLThread
|
||||
|
|
@ -58,18 +59,18 @@ public:
|
|||
int onProgress(double downloadSize, double bytesDownloaded);
|
||||
void resume(void);
|
||||
void setBandwidthLimit(U64 bytesPerSecond);
|
||||
|
||||
|
||||
private:
|
||||
curl_off_t mBandwidthLimit;
|
||||
bool mCancelled;
|
||||
LLUpdateDownloader::Client & mClient;
|
||||
CURL * mCurl;
|
||||
LLSD mDownloadData;
|
||||
llofstream mDownloadStream;
|
||||
apr_file_t* mDownloadStream;
|
||||
unsigned char mDownloadPercent;
|
||||
std::string mDownloadRecordPath;
|
||||
curl_slist * mHeaderList;
|
||||
|
||||
|
||||
void initializeCurlGet(std::string const & url, bool processHeader);
|
||||
void resumeDownloading(size_t startByte);
|
||||
void run(void);
|
||||
|
|
@ -93,7 +94,7 @@ namespace {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml";
|
||||
};
|
||||
|
||||
|
|
@ -188,22 +189,23 @@ LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client &
|
|||
mCancelled(false),
|
||||
mClient(client),
|
||||
mCurl(0),
|
||||
mDownloadStream(NULL),
|
||||
mDownloadPercent(0),
|
||||
mHeaderList(0)
|
||||
{
|
||||
CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case.
|
||||
llverify(code == CURLE_OK); // TODO: real error handling here.
|
||||
llverify(code == CURLE_OK); // TODO: real error handling here.
|
||||
}
|
||||
|
||||
|
||||
LLUpdateDownloader::Implementation::~Implementation()
|
||||
{
|
||||
if(isDownloading())
|
||||
if(isDownloading())
|
||||
{
|
||||
cancel();
|
||||
shutdown();
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
; // No op.
|
||||
}
|
||||
|
|
@ -218,7 +220,7 @@ void LLUpdateDownloader::Implementation::cancel(void)
|
|||
{
|
||||
mCancelled = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void LLUpdateDownloader::Implementation::download(LLURI const & uri,
|
||||
std::string const & hash,
|
||||
|
|
@ -259,24 +261,24 @@ void LLUpdateDownloader::Implementation::resume(void)
|
|||
mClient.downloadError("no download marker");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
LLSDSerialize::fromXMLDocument(mDownloadData, dataStream);
|
||||
|
||||
|
||||
if(!mDownloadData.asBoolean()) {
|
||||
mClient.downloadError("no download information in marker");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
std::string filePath = mDownloadData["path"].asString();
|
||||
try {
|
||||
if(LLFile::isfile(filePath)) {
|
||||
if(LLFile::isfile(filePath)) {
|
||||
llstat fileStatus;
|
||||
LLFile::stat(filePath, &fileStatus);
|
||||
if(fileStatus.st_size != mDownloadData["size"].asInteger()) {
|
||||
resumeDownloading(fileStatus.st_size);
|
||||
} else if(!validateDownload()) {
|
||||
LLFile::remove(filePath);
|
||||
download(LLURI(mDownloadData["url"].asString()),
|
||||
download(LLURI(mDownloadData["url"].asString()),
|
||||
mDownloadData["hash"].asString(),
|
||||
mDownloadData["update_version"].asString(),
|
||||
mDownloadData["required"].asBoolean());
|
||||
|
|
@ -284,7 +286,7 @@ void LLUpdateDownloader::Implementation::resume(void)
|
|||
mClient.downloadComplete(mDownloadData);
|
||||
}
|
||||
} else {
|
||||
download(LLURI(mDownloadData["url"].asString()),
|
||||
download(LLURI(mDownloadData["url"].asString()),
|
||||
mDownloadData["hash"].asString(),
|
||||
mDownloadData["update_version"].asString(),
|
||||
mDownloadData["required"].asBoolean());
|
||||
|
|
@ -301,7 +303,7 @@ void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond)
|
|||
llassert(mCurl != 0);
|
||||
mBandwidthLimit = bytesPerSecond;
|
||||
CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit);
|
||||
if(code != CURLE_OK) LL_WARNS("UpdateDownload") <<
|
||||
if(code != CURLE_OK) LL_WARNS("UpdateDownload") <<
|
||||
"unable to change dowload bandwidth" << LL_ENDL;
|
||||
} else {
|
||||
mBandwidthLimit = bytesPerSecond;
|
||||
|
|
@ -315,7 +317,7 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
|
|||
std::string header(headerPtr, headerPtr + size);
|
||||
size_t colonPosition = header.find(':');
|
||||
if(colonPosition == std::string::npos) return size; // HTML response; ignore.
|
||||
|
||||
|
||||
if(header.substr(0, colonPosition) == "Content-Length") {
|
||||
try {
|
||||
size_t firstDigitPos = header.find_first_of("0123456789", colonPosition);
|
||||
|
|
@ -323,18 +325,18 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
|
|||
std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1);
|
||||
size_t size = boost::lexical_cast<size_t>(contentLength);
|
||||
LL_INFOS("UpdateDownload") << "download size is " << size << LL_ENDL;
|
||||
|
||||
|
||||
mDownloadData["size"] = LLSD(LLSD::Integer(size));
|
||||
llofstream odataStream(mDownloadRecordPath);
|
||||
LLSDSerialize::toPrettyXML(mDownloadData, odataStream);
|
||||
} catch (std::exception const & e) {
|
||||
LL_WARNS("UpdateDownload") << "unable to read content length ("
|
||||
LL_WARNS("UpdateDownload") << "unable to read content length ("
|
||||
<< e.what() << ")" << LL_ENDL;
|
||||
}
|
||||
} else {
|
||||
; // No op.
|
||||
}
|
||||
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
|
@ -342,14 +344,11 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
|
|||
size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size)
|
||||
{
|
||||
if(mCancelled) return 0; // Forces a write error which will halt curl thread.
|
||||
if((size == 0) || (buffer == 0)) return 0;
|
||||
|
||||
mDownloadStream.write(reinterpret_cast<const char *>(buffer), size);
|
||||
if(mDownloadStream.bad()) {
|
||||
return 0;
|
||||
} else {
|
||||
return size;
|
||||
}
|
||||
if((size == 0) || (buffer == 0)) return 0;
|
||||
|
||||
apr_size_t written(size);
|
||||
ll_apr_warn_status(apr_file_write(mDownloadStream, buffer, &written));
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -358,7 +357,7 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b
|
|||
int downloadPercent = static_cast<int>(100. * (bytesDownloaded / downloadSize));
|
||||
if(downloadPercent > mDownloadPercent) {
|
||||
mDownloadPercent = downloadPercent;
|
||||
|
||||
|
||||
LLSD event;
|
||||
event["pump"] = LLUpdaterService::pumpName();
|
||||
LLSD payload;
|
||||
|
|
@ -367,12 +366,12 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b
|
|||
payload["bytes_downloaded"] = bytesDownloaded;
|
||||
event["payload"] = payload;
|
||||
LLEventPumps::instance().obtain("mainlooprepeater").post(event);
|
||||
|
||||
|
||||
LL_INFOS("UpdateDownload") << "progress event " << payload << LL_ENDL;
|
||||
} else {
|
||||
; // Keep events to a reasonalbe number.
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -380,7 +379,8 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b
|
|||
void LLUpdateDownloader::Implementation::run(void)
|
||||
{
|
||||
CURLcode code = curl_easy_perform(mCurl);
|
||||
mDownloadStream.close();
|
||||
ll_apr_warn_status(apr_file_close(mDownloadStream));
|
||||
mDownloadStream = NULL;
|
||||
if(code == CURLE_OK) {
|
||||
LLFile::remove(mDownloadRecordPath);
|
||||
if(validateDownload()) {
|
||||
|
|
@ -396,13 +396,13 @@ void LLUpdateDownloader::Implementation::run(void)
|
|||
LL_INFOS("UpdateDownload") << "download canceled by user" << LL_ENDL;
|
||||
// Do not call back client.
|
||||
} else {
|
||||
LL_WARNS("UpdateDownload") << "download failed with error '" <<
|
||||
LL_WARNS("UpdateDownload") << "download failed with error '" <<
|
||||
curl_easy_strerror(code) << "'" << LL_ENDL;
|
||||
LLFile::remove(mDownloadRecordPath);
|
||||
if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString());
|
||||
mClient.downloadError("curl error");
|
||||
}
|
||||
|
||||
|
||||
if(mHeaderList) {
|
||||
curl_slist_free_all(mHeaderList);
|
||||
mHeaderList = 0;
|
||||
|
|
@ -412,17 +412,17 @@ void LLUpdateDownloader::Implementation::run(void)
|
|||
|
||||
void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader)
|
||||
{
|
||||
if(mCurl == 0)
|
||||
if(mCurl == 0)
|
||||
{
|
||||
mCurl = LLCurl::newEasyHandle();
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
curl_easy_reset(mCurl);
|
||||
}
|
||||
|
||||
|
||||
if(mCurl == 0) throw DownloadError("failed to initialize curl");
|
||||
|
||||
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true));
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true));
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function));
|
||||
|
|
@ -439,7 +439,7 @@ void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & u
|
|||
// if it's a required update set the bandwidth limit to 0 (unlimited)
|
||||
curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit;
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, limit));
|
||||
|
||||
|
||||
mDownloadPercent = 0;
|
||||
}
|
||||
|
||||
|
|
@ -450,7 +450,7 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
|
|||
<< " at byte " << startByte << LL_ENDL;
|
||||
|
||||
initializeCurlGet(mDownloadData["url"].asString(), false);
|
||||
|
||||
|
||||
// The header 'Range: bytes n-' will request the bytes remaining in the
|
||||
// source begining with byte n and ending with the last byte.
|
||||
boost::format rangeHeaderFormat("Range: bytes=%u-");
|
||||
|
|
@ -458,9 +458,10 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
|
|||
mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str());
|
||||
if(mHeaderList == 0) throw DownloadError("cannot add Range header");
|
||||
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList));
|
||||
|
||||
mDownloadStream.open(mDownloadData["path"].asString(),
|
||||
std::ios_base::out | std::ios_base::binary | std::ios_base::app);
|
||||
|
||||
ll_apr_warn_status(apr_file_open(&mDownloadStream, mDownloadData["path"].asString().c_str(),
|
||||
APR_WRITE | APR_APPEND | APR_BINARY | APR_BUFFERED,
|
||||
APR_OS_DEFAULT, gAPRPoolp));
|
||||
start();
|
||||
}
|
||||
|
||||
|
|
@ -479,11 +480,20 @@ void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std
|
|||
LL_INFOS("UpdateDownload") << "downloading " << filePath
|
||||
<< " from " << uri.asString() << LL_ENDL;
|
||||
LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL;
|
||||
|
||||
|
||||
llofstream dataStream(mDownloadRecordPath);
|
||||
LLSDSerialize::toPrettyXML(mDownloadData, dataStream);
|
||||
|
||||
mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary);
|
||||
|
||||
ll_apr_warn_status(apr_file_open(&mDownloadStream, filePath.c_str(),
|
||||
APR_WRITE | APR_TRUNCATE | APR_BINARY | APR_BUFFERED,
|
||||
APR_OS_DEFAULT, gAPRPoolp));
|
||||
// IQA-463: Do NOT let this open file be inherited by child processes.
|
||||
// That's why we switched from llofstream to apr_file_t. From
|
||||
// apr_file_open() doc
|
||||
// http://apr.apache.org/docs/apr/1.4/group__apr__file__io.html#gabda14cbf242fb4fe99055434213e5446 :
|
||||
// "By default, the returned file descriptor will not be inherited by
|
||||
// child processes created by apr_proc_create(). This can be changed using
|
||||
// apr_file_inherit_set()."
|
||||
initializeCurlGet(uri.asString(), true);
|
||||
start();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue