Fixed web browser thanks to ArminW/Imprudence

This commit is contained in:
Siana Gearz
2010-12-24 22:22:20 +01:00
parent 80fe022718
commit 1d6bb3f3ea
59 changed files with 3505 additions and 1140 deletions

View File

@@ -3,6 +3,7 @@
project(llplugin)
include(00-Common)
include(CURL)
include(LLCommon)
include(LLImage)
include(LLMath)
@@ -19,10 +20,12 @@ include_directories(
${LLRENDER_INCLUDE_DIRS}
${LLXML_INCLUDE_DIRS}
${LLWINDOW_INCLUDE_DIRS}
${LLQTWEBKIT_INCLUDE_DIR}
)
set(llplugin_SOURCE_FILES
llpluginclassmedia.cpp
llplugincookiestore.cpp
llplugininstance.cpp
llpluginmessage.cpp
llpluginmessagepipe.cpp
@@ -36,6 +39,7 @@ set(llplugin_HEADER_FILES
llpluginclassmedia.h
llpluginclassmediaowner.h
llplugincookiestore.h
llplugininstance.h
llpluginmessage.h
llpluginmessageclasses.h
@@ -48,20 +52,32 @@ set(llplugin_HEADER_FILES
set_source_files_properties(${llplugin_HEADER_FILES}
PROPERTIES HEADER_FILE_ONLY TRUE)
if(WORD_SIZE EQUAL 64)
if(NOT CMAKE_SIZEOF_VOID_P MATCHES 4)
if(WINDOWS)
add_definitions(/FIXED:NO)
else(WINDOWS) # not windows therefore gcc LINUX and DARWIN
add_definitions(-fPIC)
endif(WINDOWS)
endif (WORD_SIZE EQUAL 64)
endif (NOT CMAKE_SIZEOF_VOID_P MATCHES 4)
list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES})
add_library (llplugin ${llplugin_SOURCE_FILES})
add_dependencies(llplugin
prepare
)
add_subdirectory(slplugin)
# # Add tests
# include(LLAddBuildTest)
# # UNIT TESTS
# SET(llplugin_TEST_SOURCE_FILES
# llplugincookiestore.cpp
# )
#
# # llplugincookiestore has a dependency on curl, so we need to link the curl library into the test.
# set_source_files_properties(
# llplugincookiestore.cpp
# PROPERTIES
# LL_TEST_ADDITIONAL_LIBRARIES "${CURL_LIBRARIES}"
# )
#
# LL_ADD_PROJECT_UNIT_TESTS(llplugin "${llplugin_TEST_SOURCE_FILES}")

93
indra/llplugin/llpluginclassmedia.cpp Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginclassmedia.cpp
* @brief LLPluginClassMedia handles a plugin which knows about the "media" message class.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#include "linden_common.h"
@@ -36,6 +39,8 @@
#include "llpluginclassmedia.h"
#include "llpluginmessageclasses.h"
#include "llqtwebkit.h"
static int LOW_PRIORITY_TEXTURE_SIZE_DEFAULT = 256;
static int nextPowerOf2( int value )
@@ -54,23 +59,31 @@ LLPluginClassMedia::LLPluginClassMedia(LLPluginClassMediaOwner *owner)
mOwner = owner;
mPlugin = NULL;
reset();
//debug use
mDeleteOK = true ;
}
LLPluginClassMedia::~LLPluginClassMedia()
{
llassert_always(mDeleteOK) ;
reset();
}
bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path)
bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug)
{
LL_DEBUGS("Plugin") << "launcher: " << launcher_filename << LL_ENDL;
LL_DEBUGS("Plugin") << "plugin: " << plugin_filename << LL_ENDL;
LL_DEBUGS("Plugin") << "user_data_path: " << user_data_path << LL_ENDL;
mPlugin = new LLPluginProcessParent(this);
mPlugin->setSleepTime(mSleepTime);
mPlugin->init(launcher_filename, plugin_filename, debug, user_data_path);
// Queue up the media init message -- it will be sent after all the currently queued messages.
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "init");
sendMessage(message);
mPlugin->init(launcher_filename, plugin_filename, debug);
return true;
}
@@ -101,6 +114,8 @@ void LLPluginClassMedia::reset()
mSetMediaHeight = -1;
mRequestedMediaWidth = 0;
mRequestedMediaHeight = 0;
mRequestedTextureWidth = 0;
mRequestedTextureHeight = 0;
mFullMediaWidth = 0;
mFullMediaHeight = 0;
mTextureWidth = 0;
@@ -123,7 +138,8 @@ void LLPluginClassMedia::reset()
mCanPaste = false;
mMediaName.clear();
mMediaDescription.clear();
mBackgroundColor = LLColor4(1.0f, 1.0f, 1.0f, 1.0f);
// media_browser class
mNavigateURI.clear();
mNavigateResultCode = -1;
@@ -132,6 +148,8 @@ void LLPluginClassMedia::reset()
mHistoryForwardAvailable = false;
mStatusText.clear();
mProgressPercent = 0;
mClickURL.clear();
mClickTarget.clear();
// media_time class
mCurrentTime = 0.0f;
@@ -147,7 +165,7 @@ void LLPluginClassMedia::idle(void)
mPlugin->idle();
}
if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL))
if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL) || (mPlugin->isBlocked()))
{
// Can't process a size change at this time
}
@@ -233,6 +251,10 @@ void LLPluginClassMedia::idle(void)
message.setValueS32("height", mRequestedMediaHeight);
message.setValueS32("texture_width", mRequestedTextureWidth);
message.setValueS32("texture_height", mRequestedTextureHeight);
message.setValueReal("background_r", mBackgroundColor.mV[VX]);
message.setValueReal("background_g", mBackgroundColor.mV[VY]);
message.setValueReal("background_b", mBackgroundColor.mV[VZ]);
message.setValueReal("background_a", mBackgroundColor.mV[VW]);
mPlugin->sendMessage(message); // DO NOT just use sendMessage() here -- we want this to jump ahead of the queue.
LL_DEBUGS("Plugin") << "Sending size_change" << LL_ENDL;
@@ -420,6 +442,12 @@ void LLPluginClassMedia::mouseEvent(EMouseEventType type, int button, int x, int
{
if(type == MOUSE_EVENT_MOVE)
{
if(!mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked())
{
// Don't queue up mouse move events that can't be delivered.
return;
}
if((x == mLastMouseX) && (y == mLastMouseY))
{
// Don't spam unnecessary mouse move events.
@@ -458,7 +486,7 @@ void LLPluginClassMedia::mouseEvent(EMouseEventType type, int button, int x, int
sendMessage(message);
}
bool LLPluginClassMedia::keyEvent(EKeyEventType type, int key_code, MASK modifiers)
bool LLPluginClassMedia::keyEvent(EKeyEventType type, int key_code, MASK modifiers, LLSD native_key_data)
{
bool result = true;
@@ -515,6 +543,7 @@ bool LLPluginClassMedia::keyEvent(EKeyEventType type, int key_code, MASK modifie
message.setValueS32("key", key_code);
message.setValue("modifiers", translateModifiers(modifiers));
message.setValueLLSD("native_key_data", native_key_data);
sendMessage(message);
}
@@ -533,12 +562,13 @@ void LLPluginClassMedia::scrollEvent(int x, int y, MASK modifiers)
sendMessage(message);
}
bool LLPluginClassMedia::textInput(const std::string &text, MASK modifiers)
bool LLPluginClassMedia::textInput(const std::string &text, MASK modifiers, LLSD native_key_data)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "text_event");
message.setValue("text", text);
message.setValue("modifiers", translateModifiers(modifiers));
message.setValueLLSD("native_key_data", native_key_data);
sendMessage(message);
@@ -663,6 +693,34 @@ void LLPluginClassMedia::paste()
sendMessage(message);
}
void LLPluginClassMedia::setUserDataPath(const std::string &user_data_path)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_user_data_path");
message.setValue("path", user_data_path);
sendMessage(message);
}
void LLPluginClassMedia::setLanguageCode(const std::string &language_code)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_language_code");
message.setValue("language", language_code);
sendMessage(message);
}
void LLPluginClassMedia::setPluginsEnabled(const bool enabled)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "plugins_enabled");
message.setValueBoolean("enable", enabled);
sendMessage(message);
}
void LLPluginClassMedia::setJavascriptEnabled(const bool enabled)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "javascript_enabled");
message.setValueBoolean("enable", enabled);
sendMessage(message);
}
/* virtual */
void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message)
{
@@ -923,6 +981,13 @@ void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message)
mClickTarget.clear();
mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW);
}
else if(message_name == "cookie_set")
{
if(mOwner)
{
mOwner->handleCookieSet(this, message.getValue("cookie"));
}
}
else
{
LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL;
@@ -1006,9 +1071,17 @@ void LLPluginClassMedia::clear_cookies()
sendMessage(message);
}
void LLPluginClassMedia::set_cookies(const std::string &cookies)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_cookies");
message.setValue("cookies", cookies);
sendMessage(message);
}
void LLPluginClassMedia::enable_cookies(bool enable)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies");
message.setValueBoolean("enable", enable);
sendMessage(message);
}

39
indra/llplugin/llpluginclassmedia.h Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginclassmedia.h
* @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGINCLASSMEDIA_H
@@ -38,7 +41,7 @@
#include "llrect.h"
#include "llpluginclassmediaowner.h"
#include <queue>
#include "v4color.h"
class LLPluginClassMedia : public LLPluginProcessParentOwner
{
@@ -48,7 +51,9 @@ public:
virtual ~LLPluginClassMedia();
// local initialization, called by the media manager when creating a source
virtual bool init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path);
virtual bool init(const std::string &launcher_filename,
const std::string &plugin_filename,
bool debug);
// undoes everything init() didm called by the media manager when destroying a source
virtual void reset();
@@ -85,6 +90,8 @@ public:
void setSize(int width, int height);
void setAutoScale(bool auto_scale);
void setBackgroundColor(LLColor4 color) { mBackgroundColor = color; };
// Returns true if all of the texture parameters (depth, format, size, and texture size) are set up and consistent.
// This will initially be false, and will also be false for some time after setSize while the resize is processed.
// Note that if this returns true, it is safe to use all the get() functions above without checking for invalid return values
@@ -111,12 +118,12 @@ public:
KEY_EVENT_REPEAT
}EKeyEventType;
bool keyEvent(EKeyEventType type, int key_code, MASK modifiers);
bool keyEvent(EKeyEventType type, int key_code, MASK modifiers, LLSD native_key_data);
void scrollEvent(int x, int y, MASK modifiers);
// Text may be unicode (utf8 encoded)
bool textInput(const std::string &text, MASK modifiers);
bool textInput(const std::string &text, MASK modifiers, LLSD native_key_data);
void loadURI(const std::string &uri);
@@ -170,6 +177,12 @@ public:
void paste();
bool canPaste() const { return mCanPaste; };
// These can be called before init(), and they will be queued and sent before the media init message.
void setUserDataPath(const std::string &user_data_path);
void setLanguageCode(const std::string &language_code);
void setPluginsEnabled(const bool enabled);
void setJavascriptEnabled(const bool enabled);
///////////////////////////////////
// media browser class functions
@@ -178,6 +191,7 @@ public:
void focus(bool focused);
void clear_cache();
void clear_cookies();
void set_cookies(const std::string &cookies);
void enable_cookies(bool enable);
void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0);
void browse_stop();
@@ -211,6 +225,7 @@ public:
// This is valid after MEDIA_EVENT_CLICK_LINK_HREF
std::string getClickTarget() const { return mClickTarget; };
std::string getMediaName() const { return mMediaName; };
std::string getMediaDescription() const { return mMediaDescription; };
@@ -327,6 +342,8 @@ protected:
std::string mMediaName;
std::string mMediaDescription;
LLColor4 mBackgroundColor;
/////////////////////////////////////////
// media_browser class
std::string mNavigateURI;
@@ -347,6 +364,14 @@ protected:
F64 mCurrentRate;
F64 mLoadedDuration;
//--------------------------------------
//debug use only
//
private:
bool mDeleteOK ;
public:
void setDeleteOK(bool flag) { mDeleteOK = flag ;}
//--------------------------------------
};
#endif // LL_LLPLUGINCLASSMEDIA_H

11
indra/llplugin/llpluginclassmediaowner.h Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginclassmediaowner.h
* @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGINCLASSMEDIAOWNER_H
@@ -38,6 +41,7 @@
#include <queue>
class LLPluginClassMedia;
class LLPluginCookieStore;
class LLPluginClassMediaOwner
{
@@ -77,6 +81,7 @@ public:
virtual ~LLPluginClassMediaOwner() {};
virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {};
virtual void handleCookieSet(LLPluginClassMedia* /*self*/, const std::string &/*cookie*/) {};
};
#endif // LL_LLPLUGINCLASSMEDIAOWNER_H

View File

@@ -0,0 +1,671 @@
/**
* @file llplugincookiestore.cpp
* @brief LLPluginCookieStore provides central storage for http cookies used by plugins
*
* @cond
* $LicenseInfo:firstyear=2010&license=viewergpl$
*
* Copyright (c) 2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#include "linden_common.h"
#include "indra_constants.h"
#include "llplugincookiestore.h"
#include <iostream>
// for curl_getdate() (apparently parsing RFC 1123 dates is hard)
#include <curl/curl.h>
LLPluginCookieStore::LLPluginCookieStore():
mHasChangedCookies(false)
{
}
LLPluginCookieStore::~LLPluginCookieStore()
{
clearCookies();
}
LLPluginCookieStore::Cookie::Cookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end):
mCookie(s, cookie_start, cookie_end - cookie_start),
mNameStart(0), mNameEnd(0),
mValueStart(0), mValueEnd(0),
mDomainStart(0), mDomainEnd(0),
mPathStart(0), mPathEnd(0),
mDead(false), mChanged(true)
{
}
LLPluginCookieStore::Cookie *LLPluginCookieStore::Cookie::createFromString(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, const std::string &host)
{
Cookie *result = new Cookie(s, cookie_start, cookie_end);
if(!result->parse(host))
{
delete result;
result = NULL;
}
return result;
}
std::string LLPluginCookieStore::Cookie::getKey() const
{
std::string result;
if(mDomainEnd > mDomainStart)
{
result += mCookie.substr(mDomainStart, mDomainEnd - mDomainStart);
}
result += ';';
if(mPathEnd > mPathStart)
{
result += mCookie.substr(mPathStart, mPathEnd - mPathStart);
}
result += ';';
result += mCookie.substr(mNameStart, mNameEnd - mNameStart);
return result;
}
bool LLPluginCookieStore::Cookie::parse(const std::string &host)
{
bool first_field = true;
std::string::size_type cookie_end = mCookie.size();
std::string::size_type field_start = 0;
LL_DEBUGS("CookieStoreParse") << "parsing cookie: " << mCookie << LL_ENDL;
while(field_start < cookie_end)
{
// Finding the start of the next field requires honoring special quoting rules
// see the definition of 'quoted-string' in rfc2616 for details
std::string::size_type next_field_start = findFieldEnd(field_start);
// The end of this field should not include the terminating ';' or any trailing whitespace
std::string::size_type field_end = mCookie.find_last_not_of("; ", next_field_start);
if(field_end == std::string::npos || field_end < field_start)
{
// This field was empty or all whitespace. Set end = start so it shows as empty.
field_end = field_start;
}
else if (field_end < next_field_start)
{
// we actually want the index of the char _after_ what 'last not of' found
++field_end;
}
// find the start of the actual name (skip separator and possible whitespace)
std::string::size_type name_start = mCookie.find_first_not_of("; ", field_start);
if(name_start == std::string::npos || name_start > next_field_start)
{
// Again, nothing but whitespace.
name_start = field_start;
}
// the name and value are separated by the first equals sign
std::string::size_type name_value_sep = mCookie.find_first_of("=", name_start);
if(name_value_sep == std::string::npos || name_value_sep > field_end)
{
// No separator found, so this is a field without an =
name_value_sep = field_end;
}
// the name end is before the name-value separator
std::string::size_type name_end = mCookie.find_last_not_of("= ", name_value_sep);
if(name_end == std::string::npos || name_end < name_start)
{
// I'm not sure how we'd hit this case... it seems like it would have to be an empty name.
name_end = name_start;
}
else if (name_end < name_value_sep)
{
// we actually want the index of the char _after_ what 'last not of' found
++name_end;
}
// Value is between the name-value sep and the end of the field.
std::string::size_type value_start = mCookie.find_first_not_of("= ", name_value_sep);
if(value_start == std::string::npos || value_start > field_end)
{
// All whitespace or empty value
value_start = field_end;
}
std::string::size_type value_end = mCookie.find_last_not_of("; ", field_end);
if(value_end == std::string::npos || value_end < value_start)
{
// All whitespace or empty value
value_end = value_start;
}
else if (value_end < field_end)
{
// we actually want the index of the char _after_ what 'last not of' found
++value_end;
}
LL_DEBUGS("CookieStoreParse")
<< " field name: \"" << mCookie.substr(name_start, name_end - name_start)
<< "\", value: \"" << mCookie.substr(value_start, value_end - value_start) << "\""
<< LL_ENDL;
// See whether this field is one we know
if(first_field)
{
// The first field is the name=value pair
mNameStart = name_start;
mNameEnd = name_end;
mValueStart = value_start;
mValueEnd = value_end;
first_field = false;
}
else
{
// Subsequent fields must come from the set in rfc2109
if(matchName(name_start, name_end, "expires"))
{
std::string date_string(mCookie, value_start, value_end - value_start);
// If the cookie contains an "expires" field, it MUST contain a parsable date.
// HACK: LLDate apparently can't PARSE an rfc1123-format date, even though it can GENERATE one.
// The curl function curl_getdate can do this, but I'm hesitant to unilaterally introduce a curl dependency in LLDate.
#if 1
time_t date = curl_getdate(date_string.c_str(), NULL );
mDate.secondsSinceEpoch((F64)date);
LL_DEBUGS("CookieStoreParse") << " expire date parsed to: " << mDate.asRFC1123() << LL_ENDL;
#else
// This doesn't work (rfc1123-format dates cause it to fail)
if(!mDate.fromString(date_string))
{
// Date failed to parse.
LL_WARNS("CookieStoreParse") << "failed to parse cookie's expire date: " << date << LL_ENDL;
return false;
}
#endif
}
else if(matchName(name_start, name_end, "domain"))
{
mDomainStart = value_start;
mDomainEnd = value_end;
}
else if(matchName(name_start, name_end, "path"))
{
mPathStart = value_start;
mPathEnd = value_end;
}
else if(matchName(name_start, name_end, "max-age"))
{
// TODO: how should we handle this?
}
else if(matchName(name_start, name_end, "secure"))
{
// We don't care about the value of this field (yet)
}
else if(matchName(name_start, name_end, "version"))
{
// We don't care about the value of this field (yet)
}
else if(matchName(name_start, name_end, "comment"))
{
// We don't care about the value of this field (yet)
}
else if(matchName(name_start, name_end, "httponly"))
{
// We don't care about the value of this field (yet)
}
else
{
// An unknown field is a parse failure
LL_WARNS("CookieStoreParse") << "unexpected field name: " << mCookie.substr(name_start, name_end - name_start) << LL_ENDL;
return false;
}
}
// move on to the next field, skipping this field's separator and any leading whitespace
field_start = mCookie.find_first_not_of("; ", next_field_start);
}
// The cookie MUST have a name
if(mNameEnd <= mNameStart)
return false;
// If the cookie doesn't have a domain, add the current host as the domain.
if(mDomainEnd <= mDomainStart)
{
if(host.empty())
{
// no domain and no current host -- this is a parse failure.
return false;
}
// Figure out whether this cookie ended with a ";" or not...
std::string::size_type last_char = mCookie.find_last_not_of(" ");
if((last_char != std::string::npos) && (mCookie[last_char] != ';'))
{
mCookie += ";";
}
mCookie += " domain=";
mDomainStart = mCookie.size();
mCookie += host;
mDomainEnd = mCookie.size();
LL_DEBUGS("CookieStoreParse") << "added domain (" << mDomainStart << " to " << mDomainEnd << "), new cookie is: " << mCookie << LL_ENDL;
}
// If the cookie doesn't have a path, add "/".
if(mPathEnd <= mPathStart)
{
// Figure out whether this cookie ended with a ";" or not...
std::string::size_type last_char = mCookie.find_last_not_of(" ");
if((last_char != std::string::npos) && (mCookie[last_char] != ';'))
{
mCookie += ";";
}
mCookie += " path=";
mPathStart = mCookie.size();
mCookie += "/";
mPathEnd = mCookie.size();
LL_DEBUGS("CookieStoreParse") << "added path (" << mPathStart << " to " << mPathEnd << "), new cookie is: " << mCookie << LL_ENDL;
}
return true;
}
std::string::size_type LLPluginCookieStore::Cookie::findFieldEnd(std::string::size_type start, std::string::size_type end)
{
std::string::size_type result = start;
if(end == std::string::npos)
end = mCookie.size();
bool in_quotes = false;
for(; (result < end); result++)
{
switch(mCookie[result])
{
case '\\':
if(in_quotes)
result++; // The next character is backslash-quoted. Skip over it.
break;
case '"':
in_quotes = !in_quotes;
break;
case ';':
if(!in_quotes)
return result;
break;
}
}
// If we got here, no ';' was found.
return end;
}
bool LLPluginCookieStore::Cookie::matchName(std::string::size_type start, std::string::size_type end, const char *name)
{
// NOTE: this assumes 'name' is already in lowercase. The code which uses it should be able to arrange this...
while((start < end) && (*name != '\0'))
{
if(tolower(mCookie[start]) != *name)
return false;
start++;
name++;
}
// iff both strings hit the end at the same time, they're equal.
return ((start == end) && (*name == '\0'));
}
std::string LLPluginCookieStore::getAllCookies()
{
std::stringstream result;
writeAllCookies(result);
return result.str();
}
void LLPluginCookieStore::writeAllCookies(std::ostream& s)
{
cookie_map_t::iterator iter;
for(iter = mCookies.begin(); iter != mCookies.end(); iter++)
{
// Don't return expired cookies
if(!iter->second->isDead())
{
s << (iter->second->getCookie()) << "\n";
}
}
}
std::string LLPluginCookieStore::getPersistentCookies()
{
std::stringstream result;
writePersistentCookies(result);
return result.str();
}
void LLPluginCookieStore::writePersistentCookies(std::ostream& s)
{
cookie_map_t::iterator iter;
for(iter = mCookies.begin(); iter != mCookies.end(); iter++)
{
// Don't return expired cookies or session cookies
if(!iter->second->isDead() && !iter->second->isSessionCookie())
{
s << iter->second->getCookie() << "\n";
}
}
}
std::string LLPluginCookieStore::getChangedCookies(bool clear_changed)
{
std::stringstream result;
writeChangedCookies(result, clear_changed);
return result.str();
}
void LLPluginCookieStore::writeChangedCookies(std::ostream& s, bool clear_changed)
{
if(mHasChangedCookies)
{
lldebugs << "returning changed cookies: " << llendl;
cookie_map_t::iterator iter;
for(iter = mCookies.begin(); iter != mCookies.end(); )
{
cookie_map_t::iterator next = iter;
next++;
// Only return cookies marked as "changed"
if(iter->second->isChanged())
{
s << iter->second->getCookie() << "\n";
lldebugs << " " << iter->second->getCookie() << llendl;
// If requested, clear the changed mark
if(clear_changed)
{
if(iter->second->isDead())
{
// If this cookie was previously marked dead, it needs to be removed entirely.
delete iter->second;
mCookies.erase(iter);
}
else
{
// Not dead, just mark as not changed.
iter->second->setChanged(false);
}
}
}
iter = next;
}
}
if(clear_changed)
mHasChangedCookies = false;
}
void LLPluginCookieStore::setAllCookies(const std::string &cookies, bool mark_changed)
{
clearCookies();
setCookies(cookies, mark_changed);
}
void LLPluginCookieStore::readAllCookies(std::istream& s, bool mark_changed)
{
clearCookies();
readCookies(s, mark_changed);
}
void LLPluginCookieStore::setCookies(const std::string &cookies, bool mark_changed)
{
std::string::size_type start = 0;
while(start != std::string::npos)
{
std::string::size_type end = cookies.find_first_of("\r\n", start);
if(end > start)
{
// The line is non-empty. Try to create a cookie from it.
setOneCookie(cookies, start, end, mark_changed);
}
start = cookies.find_first_not_of("\r\n ", end);
}
}
void LLPluginCookieStore::setCookiesFromHost(const std::string &cookies, const std::string &host, bool mark_changed)
{
std::string::size_type start = 0;
while(start != std::string::npos)
{
std::string::size_type end = cookies.find_first_of("\r\n", start);
if(end > start)
{
// The line is non-empty. Try to create a cookie from it.
setOneCookie(cookies, start, end, mark_changed, host);
}
start = cookies.find_first_not_of("\r\n ", end);
}
}
void LLPluginCookieStore::readCookies(std::istream& s, bool mark_changed)
{
std::string line;
while(s.good() && !s.eof())
{
std::getline(s, line);
if(!line.empty())
{
// Try to create a cookie from this line.
setOneCookie(line, 0, std::string::npos, mark_changed);
}
}
}
std::string LLPluginCookieStore::quoteString(const std::string &s)
{
std::stringstream result;
result << '"';
for(std::string::size_type i = 0; i < s.size(); ++i)
{
char c = s[i];
switch(c)
{
// All these separators need to be quoted in HTTP headers, according to section 2.2 of rfc 2616:
case '(': case ')': case '<': case '>': case '@':
case ',': case ';': case ':': case '\\': case '"':
case '/': case '[': case ']': case '?': case '=':
case '{': case '}': case ' ': case '\t':
result << '\\';
break;
}
result << c;
}
result << '"';
return result.str();
}
std::string LLPluginCookieStore::unquoteString(const std::string &s)
{
std::stringstream result;
bool in_quotes = false;
for(std::string::size_type i = 0; i < s.size(); ++i)
{
char c = s[i];
switch(c)
{
case '\\':
if(in_quotes)
{
// The next character is backslash-quoted. Pass it through untouched.
++i;
if(i < s.size())
{
result << s[i];
}
continue;
}
break;
case '"':
in_quotes = !in_quotes;
continue;
break;
}
result << c;
}
return result.str();
}
// The flow for deleting a cookie is non-obvious enough that I should call it out here...
// Deleting a cookie is done by setting a cookie with the same name, path, and domain, but with an expire timestamp in the past.
// (This is exactly how a web server tells a browser to delete a cookie.)
// When deleting with mark_changed set to true, this replaces the existing cookie in the list with an entry that's marked both dead and changed.
// Some time later when writeChangedCookies() is called with clear_changed set to true, the dead cookie is deleted from the list after being returned, so that the
// delete operation (in the form of the expired cookie) is passed along.
void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed, const std::string &host)
{
Cookie *cookie = Cookie::createFromString(s, cookie_start, cookie_end, host);
if(cookie)
{
LL_DEBUGS("CookieStoreUpdate") << "setting cookie: " << cookie->getCookie() << LL_ENDL;
// Create a key for this cookie
std::string key = cookie->getKey();
// Check to see whether this cookie should have expired
if(!cookie->isSessionCookie() && (cookie->getDate() < LLDate::now()))
{
// This cookie has expired.
if(mark_changed)
{
// If we're marking cookies as changed, we should keep it anyway since we'll need to send it out with deltas.
cookie->setDead(true);
LL_DEBUGS("CookieStoreUpdate") << " marking dead" << LL_ENDL;
}
else
{
// If we're not marking cookies as changed, we don't need to keep this cookie at all.
// If the cookie was already in the list, delete it.
removeCookie(key);
delete cookie;
cookie = NULL;
LL_DEBUGS("CookieStoreUpdate") << " removing" << LL_ENDL;
}
}
if(cookie)
{
// If it already exists in the map, replace it.
cookie_map_t::iterator iter = mCookies.find(key);
if(iter != mCookies.end())
{
if(iter->second->getCookie() == cookie->getCookie())
{
// The new cookie is identical to the old -- don't mark as changed.
// Just leave the old one in the map.
delete cookie;
cookie = NULL;
LL_DEBUGS("CookieStoreUpdate") << " unchanged" << LL_ENDL;
}
else
{
// A matching cookie was already in the map. Replace it.
delete iter->second;
iter->second = cookie;
cookie->setChanged(mark_changed);
if(mark_changed)
mHasChangedCookies = true;
LL_DEBUGS("CookieStoreUpdate") << " replacing" << LL_ENDL;
}
}
else
{
// The cookie wasn't in the map. Insert it.
mCookies.insert(std::make_pair(key, cookie));
cookie->setChanged(mark_changed);
if(mark_changed)
mHasChangedCookies = true;
LL_DEBUGS("CookieStoreUpdate") << " adding" << LL_ENDL;
}
}
}
else
{
LL_WARNS("CookieStoreUpdate") << "failed to parse cookie: " << s.substr(cookie_start, cookie_end - cookie_start) << LL_ENDL;
}
}
void LLPluginCookieStore::clearCookies()
{
while(!mCookies.empty())
{
cookie_map_t::iterator iter = mCookies.begin();
delete iter->second;
mCookies.erase(iter);
}
}
void LLPluginCookieStore::removeCookie(const std::string &key)
{
cookie_map_t::iterator iter = mCookies.find(key);
if(iter != mCookies.end())
{
delete iter->second;
mCookies.erase(iter);
}
}

View File

@@ -0,0 +1,127 @@
/**
* @file llplugincookiestore.h
* @brief LLPluginCookieStore provides central storage for http cookies used by plugins
*
* @cond
* $LicenseInfo:firstyear=2010&license=viewergpl$
*
* Copyright (c) 2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGINCOOKIESTORE_H
#define LL_LLPLUGINCOOKIESTORE_H
#include "lldate.h"
#include <map>
#include <string>
#include <iostream>
class LLPluginCookieStore
{
LOG_CLASS(LLPluginCookieStore);
public:
LLPluginCookieStore();
~LLPluginCookieStore();
// gets all cookies currently in storage -- use when initializing a plugin
std::string getAllCookies();
void writeAllCookies(std::ostream& s);
// gets only persistent cookies (i.e. not session cookies) -- use when writing cookies to a file
std::string getPersistentCookies();
void writePersistentCookies(std::ostream& s);
// gets cookies which are marked as "changed" -- use when sending periodic updates to plugins
std::string getChangedCookies(bool clear_changed = true);
void writeChangedCookies(std::ostream& s, bool clear_changed = true);
// (re)initializes internal data structures and bulk-sets cookies -- use when reading cookies from a file
void setAllCookies(const std::string &cookies, bool mark_changed = false);
void readAllCookies(std::istream& s, bool mark_changed = false);
// sets one or more cookies (without reinitializing anything) -- use when receiving cookies from a plugin
void setCookies(const std::string &cookies, bool mark_changed = true);
void readCookies(std::istream& s, bool mark_changed = true);
// sets one or more cookies (without reinitializing anything), supplying a hostname the cookies came from -- use when setting a cookie manually
void setCookiesFromHost(const std::string &cookies, const std::string &host, bool mark_changed = true);
// quote or unquote a string as per the definition of 'quoted-string' in rfc2616
static std::string quoteString(const std::string &s);
static std::string unquoteString(const std::string &s);
private:
void setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed, const std::string &host = LLStringUtil::null);
class Cookie
{
public:
static Cookie *createFromString(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos, const std::string &host = LLStringUtil::null);
// Construct a string from the cookie that uniquely represents it, to be used as a key in a std::map.
std::string getKey() const;
const std::string &getCookie() const { return mCookie; };
bool isSessionCookie() const { return mDate.isNull(); };
bool isDead() const { return mDead; };
void setDead(bool dead) { mDead = dead; };
bool isChanged() const { return mChanged; };
void setChanged(bool changed) { mChanged = changed; };
const LLDate &getDate() const { return mDate; };
private:
Cookie(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos);
bool parse(const std::string &host);
std::string::size_type findFieldEnd(std::string::size_type start = 0, std::string::size_type end = std::string::npos);
bool matchName(std::string::size_type start, std::string::size_type end, const char *name);
std::string mCookie; // The full cookie, in RFC 2109 string format
LLDate mDate; // The expiration date of the cookie. For session cookies, this will be a null date (mDate.isNull() is true).
// Start/end indices of various parts of the cookie string. Stored as indices into the string to save space and time.
std::string::size_type mNameStart, mNameEnd;
std::string::size_type mValueStart, mValueEnd;
std::string::size_type mDomainStart, mDomainEnd;
std::string::size_type mPathStart, mPathEnd;
bool mDead;
bool mChanged;
};
typedef std::map<std::string, Cookie*> cookie_map_t;
cookie_map_t mCookies;
bool mHasChangedCookies;
void clearCookies();
void removeCookie(const std::string &key);
};
#endif // LL_LLPLUGINCOOKIESTORE_H

9
indra/llplugin/llplugininstance.cpp Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llplugininstance.cpp
* @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#include "linden_common.h"

10
indra/llplugin/llplugininstance.h Normal file → Executable file
View File

@@ -1,10 +1,10 @@
/**
* @file llplugininstance.h
* @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +12,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +28,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGININSTANCE_H

9
indra/llplugin/llpluginmessage.cpp Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginmessage.cpp
* @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#include "linden_common.h"

10
indra/llplugin/llpluginmessage.h Normal file → Executable file
View File

@@ -1,10 +1,10 @@
/**
* @file llpluginmessage.h
* @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +12,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +28,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGINMESSAGE_H

9
indra/llplugin/llpluginmessageclasses.h Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginmessageclasses.h
* @brief This file defines the versions of existing message classes for LLPluginMessage.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGINMESSAGECLASSES_H

129
indra/llplugin/llpluginmessagepipe.cpp Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginmessagepipe.cpp
* @brief Classes that implement connections from the plugin system to pipes/pumps.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#include "linden_common.h"
@@ -95,11 +98,14 @@ void LLPluginMessagePipeOwner::killMessagePipe(void)
}
}
LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket)
LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket):
mInputMutex(gAPRPoolp),
mOutputMutex(gAPRPoolp),
mOwner(owner),
mSocket(socket)
{
mOwner = owner;
mOwner->setMessagePipe(this);
mSocket = socket;
}
LLPluginMessagePipe::~LLPluginMessagePipe()
@@ -113,6 +119,7 @@ LLPluginMessagePipe::~LLPluginMessagePipe()
bool LLPluginMessagePipe::addMessage(const std::string &message)
{
// queue the message for later output
LLMutexLock lock(&mOutputMutex);
mOutput += message;
mOutput += MESSAGE_DELIMITER; // message separator
@@ -147,6 +154,18 @@ void LLPluginMessagePipe::setSocketTimeout(apr_interval_time_t timeout_usec)
}
bool LLPluginMessagePipe::pump(F64 timeout)
{
bool result = pumpOutput();
if(result)
{
result = pumpInput(timeout);
}
return result;
}
bool LLPluginMessagePipe::pumpOutput()
{
bool result = true;
@@ -155,6 +174,7 @@ bool LLPluginMessagePipe::pump(F64 timeout)
apr_status_t status;
apr_size_t size;
LLMutexLock lock(&mOutputMutex);
if(!mOutput.empty())
{
// write any outgoing messages
@@ -182,6 +202,17 @@ bool LLPluginMessagePipe::pump(F64 timeout)
// remove the written part from the buffer and try again later.
mOutput = mOutput.substr(size);
}
else if(APR_STATUS_IS_EOF(status))
{
// This is what we normally expect when a plugin exits.
llinfos << "Got EOF from plugin socket. " << llendl;
if(mOwner)
{
mOwner->socketError(status);
}
result = false;
}
else
{
// some other error
@@ -195,6 +226,19 @@ bool LLPluginMessagePipe::pump(F64 timeout)
result = false;
}
}
}
return result;
}
bool LLPluginMessagePipe::pumpInput(F64 timeout)
{
bool result = true;
if(mSocket)
{
apr_status_t status;
apr_size_t size;
// FIXME: For some reason, the apr timeout stuff isn't working properly on windows.
// Until such time as we figure out why, don't try to use the socket timeout -- just sleep here instead.
@@ -215,8 +259,16 @@ bool LLPluginMessagePipe::pump(F64 timeout)
char input_buf[1024];
apr_size_t request_size;
// Start out by reading one byte, so that any data received will wake us up.
request_size = 1;
if(timeout == 0.0f)
{
// If we have no timeout, start out with a full read.
request_size = sizeof(input_buf);
}
else
{
// Start out by reading one byte, so that any data received will wake us up.
request_size = 1;
}
// and use the timeout so we'll sleep if no data is available.
setSocketTimeout((apr_interval_time_t)(timeout * 1000000));
@@ -235,11 +287,14 @@ bool LLPluginMessagePipe::pump(F64 timeout)
// LL_INFOS("Plugin") << "after apr_socket_recv, size = " << size << LL_ENDL;
if(size > 0)
{
LLMutexLock lock(&mInputMutex);
mInput.append(input_buf, size);
}
if(status == APR_SUCCESS)
{
// llinfos << "success, read " << size << llendl;
LL_DEBUGS("PluginSocket") << "success, read " << size << LL_ENDL;
if(size != request_size)
{
@@ -249,16 +304,28 @@ bool LLPluginMessagePipe::pump(F64 timeout)
}
else if(APR_STATUS_IS_TIMEUP(status))
{
// llinfos << "TIMEUP, read " << size << llendl;
LL_DEBUGS("PluginSocket") << "TIMEUP, read " << size << LL_ENDL;
// Timeout was hit. Since the initial read is 1 byte, this should never be a partial read.
break;
}
else if(APR_STATUS_IS_EAGAIN(status))
{
// llinfos << "EAGAIN, read " << size << llendl;
LL_DEBUGS("PluginSocket") << "EAGAIN, read " << size << LL_ENDL;
// We've been doing partial reads, and we're done now.
// Non-blocking read returned immediately.
break;
}
else if(APR_STATUS_IS_EOF(status))
{
// This is what we normally expect when a plugin exits.
LL_INFOS("PluginSocket") << "Got EOF from plugin socket. " << LL_ENDL;
if(mOwner)
{
mOwner->socketError(status);
}
result = false;
break;
}
else
@@ -275,22 +342,18 @@ bool LLPluginMessagePipe::pump(F64 timeout)
break;
}
// Second and subsequent reads should not use the timeout
setSocketTimeout(0);
// and should try to fill the input buffer
request_size = sizeof(input_buf);
if(timeout != 0.0f)
{
// Second and subsequent reads should not use the timeout
setSocketTimeout(0);
// and should try to fill the input buffer
request_size = sizeof(input_buf);
}
}
processInput();
}
}
if(!result)
{
// If we got an error, we're done.
LL_INFOS("Plugin") << "Error from socket, cleaning up." << LL_ENDL;
delete this;
}
return result;
}
@@ -298,25 +361,27 @@ bool LLPluginMessagePipe::pump(F64 timeout)
void LLPluginMessagePipe::processInput(void)
{
// Look for input delimiter(s) in the input buffer.
int start = 0;
int delim;
while((delim = mInput.find(MESSAGE_DELIMITER, start)) != std::string::npos)
mInputMutex.lock();
while((delim = mInput.find(MESSAGE_DELIMITER)) != std::string::npos)
{
// Let the owner process this message
if (mOwner)
{
mOwner->receiveMessageRaw(mInput.substr(start, delim - start));
// Pull the message out of the input buffer before calling receiveMessageRaw.
// It's now possible for this function to get called recursively (in the case where the plugin makes a blocking request)
// and this guarantees that the messages will get dequeued correctly.
std::string message(mInput, 0, delim);
mInput.erase(0, delim + 1);
mInputMutex.unlock();
mOwner->receiveMessageRaw(message);
mInputMutex.lock();
}
else
{
LL_WARNS("Plugin") << "!mOwner" << LL_ENDL;
}
start = delim + 1;
}
// Remove delivered messages from the input buffer.
if(start != 0)
mInput = mInput.substr(start);
mInputMutex.unlock();
}

18
indra/llplugin/llpluginmessagepipe.h Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginmessagepipe.h
* @brief Classes that implement connections from the plugin system to pipes/pumps.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,12 +29,15 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGINMESSAGEPIPE_H
#define LL_LLPLUGINMESSAGEPIPE_H
#include "lliosocket.h"
#include "llthread.h"
class LLPluginMessagePipe;
@@ -50,7 +54,7 @@ public:
virtual apr_status_t socketError(apr_status_t error);
// called from LLPluginMessagePipe to manage the connection with LLPluginMessagePipeOwner -- do not use!
virtual void setMessagePipe(LLPluginMessagePipe *message_pipe) ;
virtual void setMessagePipe(LLPluginMessagePipe *message_pipe);
protected:
// returns false if writeMessageRaw() would drop the message
@@ -75,14 +79,18 @@ public:
void clearOwner(void);
bool pump(F64 timeout = 0.0f);
bool pumpOutput();
bool pumpInput(F64 timeout = 0.0f);
protected:
void processInput(void);
// used internally by pump()
void setSocketTimeout(apr_interval_time_t timeout_usec);
LLMutex mInputMutex;
std::string mInput;
LLMutex mOutputMutex;
std::string mOutput;
LLPluginMessagePipeOwner *mOwner;

107
indra/llplugin/llpluginprocesschild.cpp Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginprocesschild.cpp
* @brief LLPluginProcessChild handles the child side of the external-process plugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#include "linden_common.h"
@@ -42,10 +45,13 @@ static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will
LLPluginProcessChild::LLPluginProcessChild()
{
mState = STATE_UNINITIALIZED;
mInstance = NULL;
mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz
mCPUElapsed = 0.0f;
mBlockingRequest = false;
mBlockingResponseReceived = false;
}
LLPluginProcessChild::~LLPluginProcessChild()
@@ -58,7 +64,7 @@ LLPluginProcessChild::~LLPluginProcessChild()
// appears to fail and lock up which means that a given instance of the slplugin process never exits.
// This is bad, especially when users try to update their version of SL - it fails because the slplugin
// process as well as a bunch of plugin specific files are locked and cannot be overwritten.
exit(0);
exit( 0 );
//delete mInstance;
//mInstance = NULL;
}
@@ -81,9 +87,14 @@ void LLPluginProcessChild::idle(void)
bool idle_again;
do
{
if(mSocketError != APR_SUCCESS)
if(APR_STATUS_IS_EOF(mSocketError))
{
LL_INFOS("Plugin") << "message pipe is in error state, moving to STATE_ERROR"<< LL_ENDL;
// Plugin socket was closed. This covers both normal plugin termination and host crashes.
setState(STATE_ERROR);
}
else if(mSocketError != APR_SUCCESS)
{
LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR"<< LL_ENDL;
setState(STATE_ERROR);
}
@@ -153,7 +164,6 @@ void LLPluginProcessChild::idle(void)
{
setState(STATE_PLUGIN_INITIALIZING);
LLPluginMessage message("base", "init");
message.setValue("user_data_path", mUserDataPath);
sendMessageToPlugin(message);
}
break;
@@ -225,6 +235,7 @@ void LLPluginProcessChild::idle(void)
void LLPluginProcessChild::sleep(F64 seconds)
{
deliverQueuedMessages();
if(mMessagePipe)
{
mMessagePipe->pump(seconds);
@@ -237,6 +248,7 @@ void LLPluginProcessChild::sleep(F64 seconds)
void LLPluginProcessChild::pump(void)
{
deliverQueuedMessages();
if(mMessagePipe)
{
mMessagePipe->pump(0.0f);
@@ -278,14 +290,14 @@ void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message)
{
if (mInstance)
{
std::string buffer = message.generate();
LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL;
LLTimer elapsed;
mInstance->sendMessage(buffer);
mCPUElapsed += elapsed.getElapsedTimeF64();
std::string buffer = message.generate();
LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL;
LLTimer elapsed;
mInstance->sendMessage(buffer);
mCPUElapsed += elapsed.getElapsedTimeF64();
}
else
{
@@ -308,15 +320,32 @@ void LLPluginProcessChild::receiveMessageRaw(const std::string &message)
LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL;
// Decode this message
LLPluginMessage parsed;
parsed.parse(message);
if(mBlockingRequest)
{
// We're blocking the plugin waiting for a response.
if(parsed.hasValue("blocking_response"))
{
// This is the message we've been waiting for -- fall through and send it immediately.
mBlockingResponseReceived = true;
}
else
{
// Still waiting. Queue this message and don't process it yet.
mMessageQueue.push(message);
return;
}
}
bool passMessage = true;
// FIXME: how should we handle queueing here?
{
// Decode this message
LLPluginMessage parsed;
parsed.parse(message);
std::string message_class = parsed.getClass();
if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
{
@@ -326,7 +355,6 @@ void LLPluginProcessChild::receiveMessageRaw(const std::string &message)
if(message_name == "load_plugin")
{
mPluginFile = parsed.getValue("file");
mUserDataPath = parsed.getValue("user_data_path");
}
else if(message_name == "shm_add")
{
@@ -425,7 +453,13 @@ void LLPluginProcessChild::receiveMessageRaw(const std::string &message)
void LLPluginProcessChild::receivePluginMessage(const std::string &message)
{
LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL;
if(mBlockingRequest)
{
//
LL_ERRS("Plugin") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL;
}
// Incoming message from the plugin instance
bool passMessage = true;
@@ -436,6 +470,12 @@ void LLPluginProcessChild::receivePluginMessage(const std::string &message)
// Decode this message
LLPluginMessage parsed;
parsed.parse(message);
if(parsed.hasValue("blocking_request"))
{
mBlockingRequest = true;
}
std::string message_class = parsed.getClass();
if(message_class == "base")
{
@@ -494,6 +534,19 @@ void LLPluginProcessChild::receivePluginMessage(const std::string &message)
LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL;
writeMessageRaw(message);
}
while(mBlockingRequest)
{
// The plugin wants to block and wait for a response to this message.
sleep(mSleepTime); // this will pump the message pipe and process messages
if(mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL))
{
// Response has been received, or we've hit an error state. Stop waiting.
mBlockingRequest = false;
mBlockingResponseReceived = false;
}
}
}
@@ -502,3 +555,15 @@ void LLPluginProcessChild::setState(EState state)
LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
mState = state;
};
void LLPluginProcessChild::deliverQueuedMessages()
{
if(!mBlockingRequest)
{
while(!mMessageQueue.empty())
{
receiveMessageRaw(mMessageQueue.front());
mMessageQueue.pop();
}
}
}

21
indra/llplugin/llpluginprocesschild.h Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginprocesschild.h
* @brief LLPluginProcessChild handles the child side of the external-process plugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,11 +29,15 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGINPROCESSCHILD_H
#define LL_LLPLUGINPROCESSCHILD_H
#include <queue> //imprudence
#include "llpluginmessage.h"
#include "llpluginmessagepipe.h"
#include "llplugininstance.h"
@@ -88,16 +93,15 @@ private:
STATE_ERROR, // generic bailout state
STATE_DONE // state machine will sit in this state after either error or normal termination.
};
EState mState;
void setState(EState state);
EState mState;
LLHost mLauncherHost;
LLSocket::ptr_t mSocket;
std::string mPluginFile;
std::string mUserDataPath;
LLPluginInstance *mInstance;
typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
@@ -106,6 +110,11 @@ private:
LLTimer mHeartbeat;
F64 mSleepTime;
F64 mCPUElapsed;
bool mBlockingRequest;
bool mBlockingResponseReceived;
std::queue<std::string> mMessageQueue;
void deliverQueuedMessages();
};

536
indra/llplugin/llpluginprocessparent.cpp Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginprocessparent.cpp
* @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#include "linden_common.h"
@@ -44,25 +47,90 @@ LLPluginProcessParentOwner::~LLPluginProcessParentOwner()
}
LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner)
bool LLPluginProcessParent::sUseReadThread = false;
apr_pollset_t *LLPluginProcessParent::sPollSet = NULL;
bool LLPluginProcessParent::sPollsetNeedsRebuild = false;
LLMutex *LLPluginProcessParent::sInstancesMutex;
std::list<LLPluginProcessParent*> LLPluginProcessParent::sInstances;
LLThread *LLPluginProcessParent::sReadThread = NULL;
class LLPluginProcessParentPollThread: public LLThread
{
public:
LLPluginProcessParentPollThread() :
LLThread("LLPluginProcessParentPollThread", gAPRPoolp)
{
}
protected:
// Inherited from LLThread
/*virtual*/ void run(void)
{
while(!isQuitting() && LLPluginProcessParent::getUseReadThread())
{
LLPluginProcessParent::poll(0.1f);
checkPause();
}
// Final poll to clean up the pollset, etc.
LLPluginProcessParent::poll(0.0f);
}
// Inherited from LLThread
/*virtual*/ bool runCondition(void)
{
return(LLPluginProcessParent::canPollThreadRun());
}
};
LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner):
mIncomingQueueMutex(gAPRPoolp)
{
if(!sInstancesMutex)
{
sInstancesMutex = new LLMutex(gAPRPoolp);
}
mOwner = owner;
mBoundPort = 0;
mState = STATE_UNINITIALIZED;
mSleepTime = 0.0;
mCPUUsage = 0.0;
mDisableTimeout = false;
mDebug = false;
mBlocked = false;
mPolledInput = false;
mPollFD.client_data = NULL;
mPluginLaunchTimeout = 60.0f;
mPluginLockupTimeout = 15.0f;
// Don't start the timer here -- start it when we actually launch the plugin process.
mHeartbeat.stop();
// Don't add to the global list until fully constructed.
{
LLMutexLock lock(sInstancesMutex);
sInstances.push_back(this);
}
}
LLPluginProcessParent::~LLPluginProcessParent()
{
LL_DEBUGS("Plugin") << "destructor" << LL_ENDL;
// Remove from the global list before beginning destruction.
{
// Make sure to get the global mutex _first_ here, to avoid a possible deadlock against LLPluginProcessParent::poll()
LLMutexLock lock(sInstancesMutex);
{
LLMutexLock lock2(&mIncomingQueueMutex);
sInstances.remove(this);
}
}
// Destroy any remaining shared memory regions
sharedMemoryRegionsType::iterator iter;
while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end())
@@ -74,15 +142,17 @@ LLPluginProcessParent::~LLPluginProcessParent()
mSharedMemoryRegions.erase(iter);
}
// orphaning the process means it won't be killed when the LLProcessLauncher is destructed.
// This is what we want -- it should exit cleanly once it notices the sockets have been closed.
mProcess.orphan();
mProcess.kill();
killSockets();
}
void LLPluginProcessParent::killSockets(void)
{
killMessagePipe();
{
LLMutexLock lock(&mIncomingQueueMutex);
killMessagePipe();
}
mListenSocket.reset();
mSocket.reset();
}
@@ -95,14 +165,12 @@ void LLPluginProcessParent::errorState(void)
setState(STATE_ERROR);
}
void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path)
void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug)
{
mProcess.setExecutable(launcher_filename);
mPluginFile = plugin_filename;
mCPUUsage = 0.0f;
mDebug = debug;
mUserDataPath = user_data_path;
mDebug = debug;
setState(STATE_INITIALIZED);
}
@@ -158,21 +226,47 @@ void LLPluginProcessParent::idle(void)
do
{
// process queued messages
mIncomingQueueMutex.lock();
while(!mIncomingQueue.empty())
{
LLPluginMessage message = mIncomingQueue.front();
mIncomingQueue.pop();
mIncomingQueueMutex.unlock();
receiveMessage(message);
mIncomingQueueMutex.lock();
}
mIncomingQueueMutex.unlock();
// Give time to network processing
if(mMessagePipe)
{
if(!mMessagePipe->pump())
// Drain any queued outgoing messages
mMessagePipe->pumpOutput();
// Only do input processing here if this instance isn't in a pollset.
if(!mPolledInput)
{
// LL_WARNS("Plugin") << "Message pipe hit an error state" << LL_ENDL;
errorState();
mMessagePipe->pumpInput();
}
}
if((mSocketError != APR_SUCCESS) && (mState <= STATE_RUNNING))
if(mState <= STATE_RUNNING)
{
// The socket is in an error state -- the plugin is gone.
LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL;
errorState();
if(APR_STATUS_IS_EOF(mSocketError))
{
// Plugin socket was closed. This covers both normal plugin termination and plugin crashes.
errorState();
}
else if(mSocketError != APR_SUCCESS)
{
// The socket is in an error state -- the plugin is gone.
LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL;
errorState();
}
}
// If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState().
@@ -314,6 +408,17 @@ void LLPluginProcessParent::idle(void)
mDebugger.addArgument("end tell");
mDebugger.launch();
#elif LL_LINUX
std::stringstream cmd;
mDebugger.setExecutable("/usr/bin/gnome-terminal");
mDebugger.addArgument("--geometry=165x24-0+0");
mDebugger.addArgument("-e");
cmd << "/usr/bin/gdb -n /proc/" << mProcess.getProcessID() << "/exe " << mProcess.getProcessID();
mDebugger.addArgument(cmd.str());
mDebugger.launch();
#endif
}
@@ -353,13 +458,12 @@ void LLPluginProcessParent::idle(void)
break;
case STATE_HELLO:
LL_DEBUGS("Plugin") << "received hello message" << llendl;
LL_DEBUGS("Plugin") << "received hello message" << LL_ENDL;
// Send the message to load the plugin
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin");
message.setValue("file", mPluginFile);
message.setValue("user_data_path", mUserDataPath);
sendMessage(message);
}
@@ -388,7 +492,7 @@ void LLPluginProcessParent::idle(void)
}
else if(pluginLockedUp())
{
LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << llendl;
LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << LL_ENDL;
errorState();
}
break;
@@ -410,8 +514,7 @@ void LLPluginProcessParent::idle(void)
break;
case STATE_CLEANUP:
// Don't do a kill here anymore -- closing the sockets is the new 'kill'.
mProcess.orphan();
mProcess.kill();
killSockets();
setState(STATE_DONE);
break;
@@ -479,23 +582,323 @@ void LLPluginProcessParent::setSleepTime(F64 sleep_time, bool force_send)
void LLPluginProcessParent::sendMessage(const LLPluginMessage &message)
{
if(message.hasValue("blocking_response"))
{
mBlocked = false;
// reset the heartbeat timer, since there will have been no heartbeats while the plugin was blocked.
mHeartbeat.setTimerExpirySec(mPluginLockupTimeout);
}
std::string buffer = message.generate();
LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL;
writeMessageRaw(buffer);
// Try to send message immediately.
if(mMessagePipe)
{
mMessagePipe->pumpOutput();
}
}
//virtual
void LLPluginProcessParent::setMessagePipe(LLPluginMessagePipe *message_pipe)
{
bool update_pollset = false;
if(mMessagePipe)
{
// Unsetting an existing message pipe -- remove from the pollset
mPollFD.client_data = NULL;
// pollset needs an update
update_pollset = true;
}
if(message_pipe != NULL)
{
// Set up the apr_pollfd_t
mPollFD.p = gAPRPoolp;
mPollFD.desc_type = APR_POLL_SOCKET;
mPollFD.reqevents = APR_POLLIN|APR_POLLERR|APR_POLLHUP;
mPollFD.rtnevents = 0;
mPollFD.desc.s = mSocket->getSocket();
mPollFD.client_data = (void*)this;
// pollset needs an update
update_pollset = true;
}
mMessagePipe = message_pipe;
if(update_pollset)
{
dirtyPollSet();
}
}
//static
void LLPluginProcessParent::dirtyPollSet()
{
sPollsetNeedsRebuild = true;
if(sReadThread)
{
LL_DEBUGS("PluginPoll") << "unpausing read thread " << LL_ENDL;
sReadThread->unpause();
}
}
void LLPluginProcessParent::updatePollset()
{
if(!sInstancesMutex)
{
// No instances have been created yet. There's no work to do.
return;
}
LLMutexLock lock(sInstancesMutex);
if(sPollSet)
{
LL_DEBUGS("PluginPoll") << "destroying pollset " << sPollSet << LL_ENDL;
// delete the existing pollset.
apr_pollset_destroy(sPollSet);
sPollSet = NULL;
}
std::list<LLPluginProcessParent*>::iterator iter;
int count = 0;
// Count the number of instances that want to be in the pollset
for(iter = sInstances.begin(); iter != sInstances.end(); iter++)
{
(*iter)->mPolledInput = false;
if((*iter)->mPollFD.client_data)
{
// This instance has a socket that needs to be polled.
++count;
}
}
if(sUseReadThread && sReadThread && !sReadThread->isQuitting())
{
if(!sPollSet && (count > 0))
{
#ifdef APR_POLLSET_NOCOPY
// The pollset doesn't exist yet. Create it now.
apr_status_t status = apr_pollset_create(&sPollSet, count, gAPRPoolp, APR_POLLSET_NOCOPY);
if(status != APR_SUCCESS)
{
#endif // APR_POLLSET_NOCOPY
LL_WARNS("PluginPoll") << "Couldn't create pollset. Falling back to non-pollset mode." << LL_ENDL;
sPollSet = NULL;
#ifdef APR_POLLSET_NOCOPY
}
else
{
LL_DEBUGS("PluginPoll") << "created pollset " << sPollSet << LL_ENDL;
// Pollset was created, add all instances to it.
for(iter = sInstances.begin(); iter != sInstances.end(); iter++)
{
if((*iter)->mPollFD.client_data)
{
status = apr_pollset_add(sPollSet, &((*iter)->mPollFD));
if(status == APR_SUCCESS)
{
(*iter)->mPolledInput = true;
}
else
{
LL_WARNS("PluginPoll") << "apr_pollset_add failed with status " << status << LL_ENDL;
}
}
}
}
#endif // APR_POLLSET_NOCOPY
}
}
}
void LLPluginProcessParent::setUseReadThread(bool use_read_thread)
{
if(sUseReadThread != use_read_thread)
{
sUseReadThread = use_read_thread;
if(sUseReadThread)
{
if(!sReadThread)
{
// start up the read thread
LL_INFOS("PluginPoll") << "creating read thread " << LL_ENDL;
// make sure the pollset gets rebuilt.
sPollsetNeedsRebuild = true;
sReadThread = new LLPluginProcessParentPollThread;
sReadThread->start();
}
}
else
{
if(sReadThread)
{
// shut down the read thread
LL_INFOS("PluginPoll") << "destroying read thread " << LL_ENDL;
delete sReadThread;
sReadThread = NULL;
}
}
}
}
void LLPluginProcessParent::poll(F64 timeout)
{
if(sPollsetNeedsRebuild || !sUseReadThread)
{
sPollsetNeedsRebuild = false;
updatePollset();
}
if(sPollSet)
{
apr_status_t status;
apr_int32_t count;
const apr_pollfd_t *descriptors;
status = apr_pollset_poll(sPollSet, (apr_interval_time_t)(timeout * 1000000), &count, &descriptors);
if(status == APR_SUCCESS)
{
// One or more of the descriptors signalled. Call them.
for(int i = 0; i < count; i++)
{
LLPluginProcessParent *self = (LLPluginProcessParent *)(descriptors[i].client_data);
// NOTE: the descriptor returned here is actually a COPY of the original (even though we create the pollset with APR_POLLSET_NOCOPY).
// This means that even if the parent has set its mPollFD.client_data to NULL, the old pointer may still there in this descriptor.
// It's even possible that the old pointer no longer points to a valid LLPluginProcessParent.
// This means that we can't safely dereference the 'self' pointer here without some extra steps...
if(self)
{
// Make sure this pointer is still in the instances list
bool valid = false;
{
LLMutexLock lock(sInstancesMutex);
for(std::list<LLPluginProcessParent*>::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter)
{
if(*iter == self)
{
// Lock the instance's mutex before unlocking the global mutex.
// This avoids a possible race condition where the instance gets deleted between this check and the servicePoll() call.
self->mIncomingQueueMutex.lock();
valid = true;
break;
}
}
}
if(valid)
{
// The instance is still valid.
// Pull incoming messages off the socket
self->servicePoll();
self->mIncomingQueueMutex.unlock();
}
else
{
LL_DEBUGS("PluginPoll") << "detected deleted instance " << self << LL_ENDL;
}
}
}
}
else if(APR_STATUS_IS_TIMEUP(status))
{
// timed out with no incoming data. Just return.
}
else if(status == EBADF)
{
// This happens when one of the file descriptors in the pollset is destroyed, which happens whenever a plugin's socket is closed.
// The pollset has been or will be recreated, so just return.
LL_DEBUGS("PluginPoll") << "apr_pollset_poll returned EBADF" << LL_ENDL;
}
else if(status != APR_SUCCESS)
{
LL_WARNS("PluginPoll") << "apr_pollset_poll failed with status " << status << LL_ENDL;
}
}
}
void LLPluginProcessParent::servicePoll()
{
bool result = true;
// poll signalled on this object's socket. Try to process incoming messages.
if(mMessagePipe)
{
result = mMessagePipe->pumpInput(0.0f);
}
if(!result)
{
// If we got a read error on input, remove this pipe from the pollset
apr_pollset_remove(sPollSet, &mPollFD);
// and tell the code not to re-add it
mPollFD.client_data = NULL;
}
}
void LLPluginProcessParent::receiveMessageRaw(const std::string &message)
{
LL_DEBUGS("Plugin") << "Received: " << message << LL_ENDL;
// FIXME: should this go into a queue instead?
LLPluginMessage parsed;
if(parsed.parse(message) != -1)
{
receiveMessage(parsed);
if(parsed.hasValue("blocking_request"))
{
mBlocked = true;
}
if(mPolledInput)
{
// This is being called on the polling thread -- only do minimal processing/queueing.
receiveMessageEarly(parsed);
}
else
{
// This is not being called on the polling thread -- do full message processing at this time.
receiveMessage(parsed);
}
}
}
void LLPluginProcessParent::receiveMessageEarly(const LLPluginMessage &message)
{
// NOTE: this function will be called from the polling thread. It will be called with mIncomingQueueMutex _already locked_.
bool handled = false;
std::string message_class = message.getClass();
if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
{
// no internal messages need to be handled early.
}
else
{
// Call out to the owner and see if they to reply
// TODO: Should this only happen when blocked?
if(mOwner != NULL)
{
handled = mOwner->receivePluginMessageEarly(message);
}
}
if(!handled)
{
// any message that wasn't handled early needs to be queued.
mIncomingQueue.push(message);
}
}
@@ -681,7 +1084,7 @@ std::string LLPluginProcessParent::getPluginVersion(void)
void LLPluginProcessParent::setState(EState state)
{
LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
LL_DEBUGS("Plugin") << "setting state to " << stateToString(state) << LL_ENDL;
mState = state;
};
@@ -689,18 +1092,15 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()
{
bool result = false;
if(!mDisableTimeout && !mDebug)
if(!mProcess.isRunning())
{
if(!mProcess.isRunning())
{
LL_WARNS("Plugin") << "child exited" << llendl;
result = true;
}
else if(pluginLockedUp())
{
LL_WARNS("Plugin") << "timeout" << llendl;
result = true;
}
LL_WARNS("Plugin") << "child exited" << LL_ENDL;
result = true;
}
else if(pluginLockedUp())
{
LL_WARNS("Plugin") << "timeout" << LL_ENDL;
result = true;
}
return result;
@@ -708,7 +1108,63 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()
bool LLPluginProcessParent::pluginLockedUp()
{
if(mDisableTimeout || mDebug || mBlocked)
{
// Never time out a plugin process in these cases.
return false;
}
// If the timer is running and has expired, the plugin has locked up.
return (mHeartbeat.getStarted() && mHeartbeat.hasExpired());
}
std::string LLPluginProcessParent::stateToString(EState state)
{
std::string eng = "unknown plugin state";
switch (state)
{
case STATE_UNINITIALIZED:
eng = "STATE_UNINITIALIZED";
break;
case STATE_INITIALIZED:
eng = "STATE_INITIALIZED - init() has been called";
break;
case STATE_LISTENING:
eng = "STATE_LISTENING - listening for incoming connection";
break;
case STATE_LAUNCHED:
eng = "STATE_LAUNCHED - process has been launched";
break;
case STATE_CONNECTED:
eng = "STATE_CONNECTED - process has connected";
break;
case STATE_HELLO:
eng = "STATE_HELLO - first message from the plugin process has been received";
break;
case STATE_LOADING:
eng = "STATE_LOADING - process has been asked to load the plugin";
break;
case STATE_RUNNING:
eng = "STATE_RUNNING - plugin running";
break;
case STATE_LAUNCH_FAILURE:
eng = "STATE_LAUNCH_FAILURE - failure before plugin loaded";
break;
case STATE_ERROR:
eng = "STATE_ERROR - generic bailout state";
break;
case STATE_CLEANUP:
eng = "STATE_CLEANUP - clean everything up";
break;
case STATE_EXITING:
eng = "STATE_EXITING - tried to kill process, waiting for it to exit";
break;
case STATE_DONE:
eng = "STATE_DONE - plugin done";
break;
default:
break;
}
return llformat("(%d) ", (S32)state) + eng;
}

49
indra/llplugin/llpluginprocessparent.h Normal file → Executable file
View File

@@ -2,9 +2,10 @@
* @file llpluginprocessparent.h
* @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,11 +29,15 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGINPROCESSPARENT_H
#define LL_LLPLUGINPROCESSPARENT_H
#include <queue> //imprudence
#include "llapr.h"
#include "llprocesslauncher.h"
#include "llpluginmessage.h"
@@ -40,12 +45,14 @@
#include "llpluginsharedmemory.h"
#include "lliosocket.h"
#include "llthread.h"
class LLPluginProcessParentOwner
{
public:
virtual ~LLPluginProcessParentOwner();
virtual void receivePluginMessage(const LLPluginMessage &message) = 0;
virtual bool receivePluginMessageEarly(const LLPluginMessage &message) {return false;};
// This will only be called when the plugin has died unexpectedly
virtual void pluginLaunchFailed() {};
virtual void pluginDied() {};
@@ -58,7 +65,10 @@ public:
LLPluginProcessParent(LLPluginProcessParentOwner *owner);
~LLPluginProcessParent();
void init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path);
void init(const std::string &launcher_filename,
const std::string &plugin_filename,
bool debug);
void idle(void);
// returns true if the plugin is on its way to steady state
@@ -70,6 +80,9 @@ public:
// returns true if the process has exited or we've had a fatal error
bool isDone(void);
// returns true if the process is currently waiting on a blocking request
bool isBlocked(void) { return mBlocked; };
void killSockets(void);
// Go to the proper error state
@@ -83,7 +96,9 @@ public:
void receiveMessage(const LLPluginMessage &message);
// Inherited from LLPluginMessagePipeOwner
void receiveMessageRaw(const std::string &message);
/*virtual*/ void receiveMessageRaw(const std::string &message);
/*virtual*/ void receiveMessageEarly(const LLPluginMessage &message);
/*virtual*/ void setMessagePipe(LLPluginMessagePipe *message_pipe) ;
// This adds a memory segment shared with the client, generating a name for the segment. The name generated is guaranteed to be unique on the host.
// The caller must call removeSharedMemory first (and wait until getSharedMemorySize returns 0 for the indicated name) before re-adding a segment with the same name.
@@ -106,7 +121,11 @@ public:
void setLockupTimeout(F32 timeout) { mPluginLockupTimeout = timeout; };
F64 getCPUUsage() { return mCPUUsage; };
static void poll(F64 timeout);
static bool canPollThreadRun() { return (sPollSet || sPollsetNeedsRebuild || sUseReadThread); };
static void setUseReadThread(bool use_read_thread);
static bool getUseReadThread() { return sUseReadThread; };
private:
enum EState
@@ -128,6 +147,7 @@ private:
};
EState mState;
void setState(EState state);
std::string stateToString(EState state);
bool pluginLockedUp();
bool pluginLockedUpOrQuit();
@@ -142,8 +162,6 @@ private:
std::string mPluginFile;
std::string mUserDataPath;
LLPluginProcessParentOwner *mOwner;
typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
@@ -158,12 +176,27 @@ private:
bool mDisableTimeout;
bool mDebug;
bool mBlocked;
bool mPolledInput;
LLProcessLauncher mDebugger;
F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch.
F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up.
static bool sUseReadThread;
apr_pollfd_t mPollFD;
static apr_pollset_t *sPollSet;
static bool sPollsetNeedsRebuild;
static LLMutex *sInstancesMutex;
static std::list<LLPluginProcessParent*> sInstances;
static void dirtyPollSet();
static void updatePollset();
void servicePoll();
static LLThread *sReadThread;
LLMutex mIncomingQueueMutex;
std::queue<LLPluginMessage> mIncomingQueue;
};
#endif // LL_LLPLUGINPROCESSPARENT_H

11
indra/llplugin/llpluginsharedmemory.cpp Normal file → Executable file
View File

@@ -1,10 +1,11 @@
/**
* @file llpluginsharedmemory.cpp
* @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
* LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#include "linden_common.h"

10
indra/llplugin/llpluginsharedmemory.h Normal file → Executable file
View File

@@ -1,10 +1,10 @@
/**
* @file llpluginsharedmemory.h
* @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +12,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +28,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#ifndef LL_LLPLUGINSHAREDMEMORY_H

42
indra/llplugin/slplugin/CMakeLists.txt Normal file → Executable file
View File

@@ -16,6 +16,7 @@ include_directories(
if (DARWIN)
include(CMakeFindFrameworks)
find_library(CARBON_LIBRARY Carbon)
find_library(COCOA_LIBRARY Cocoa)
endif (DARWIN)
@@ -25,11 +26,33 @@ set(SLPlugin_SOURCE_FILES
slplugin.cpp
)
if (DARWIN)
list(APPEND SLPlugin_SOURCE_FILES
slplugin-objc.mm
)
list(APPEND SLPlugin_HEADER_FILES
slplugin-objc.h
)
endif (DARWIN)
set_source_files_properties(${SLPlugin_HEADER_FILES}
PROPERTIES HEADER_FILE_ONLY TRUE)
if (SLPlugin_HEADER_FILES)
list(APPEND SLPlugin_SOURCE_FILES ${SLPlugin_HEADER_FILES})
endif (SLPlugin_HEADER_FILES)
add_executable(SLPlugin
WIN32
MACOSX_BUNDLE
${SLPlugin_SOURCE_FILES}
)
set_target_properties(SLPlugin
PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist
)
target_link_libraries(SLPlugin
${LLPLUGIN_LIBRARIES}
${LLMESSAGE_LIBRARIES}
@@ -44,15 +67,16 @@ add_dependencies(SLPlugin
)
if (DARWIN)
# Mac version needs to link against carbon, and also needs an embedded plist (to set LSBackgroundOnly)
target_link_libraries(SLPlugin ${CARBON_LIBRARY})
set_target_properties(
SLPlugin
PROPERTIES
LINK_FLAGS "-Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist"
# Mac version needs to link against Carbon
target_link_libraries(SLPlugin ${CARBON_LIBRARY} ${COCOA_LIBRARY})
# Make sure the app bundle has a Resources directory (it will get populated by viewer-manifest.py later)
add_custom_command(
TARGET SLPlugin POST_BUILD
COMMAND mkdir
ARGS
-p
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/SLPlugin.app/Contents/Resources
)
endif (DARWIN)
if (LINUX)
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lrt")
endif (LINUX)
#ll_deploy_sharedlibs_command(SLPlugin)

View File

@@ -0,0 +1,42 @@
/**
* @file slplugin-objc.h
* @brief Header file for slplugin-objc.mm.
*
* @cond
*
* $LicenseInfo:firstyear=2010&license=viewergpl$
*
* Copyright (c) 2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
*
* @endcond
*/
/* Defined in slplugin-objc.mm: */
void setupCocoa();
void createAutoReleasePool();
void deleteAutoReleasePool();

View File

@@ -0,0 +1,89 @@
/**
* @file slplugin-objc.mm
* @brief Objective-C++ file for use with the loader shell, so we can use a couple of Cocoa APIs.
*
* @cond
*
* $LicenseInfo:firstyear=2010&license=viewergpl$
*
* Copyright (c) 2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
*
* @endcond
*/
#include <AppKit/AppKit.h>
#include "slplugin-objc.h"
void setupCocoa()
{
static bool inited = false;
if(!inited)
{
createAutoReleasePool();
// The following prevents the Cocoa command line parser from trying to open 'unknown' arguements as documents.
// ie. running './secondlife -set Language fr' would cause a pop-up saying can't open document 'fr'
// when init'ing the Cocoa App window.
[[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"];
// This is a bit of voodoo taken from the Apple sample code "CarbonCocoa_PictureCursor":
// http://developer.apple.com/samplecode/CarbonCocoa_PictureCursor/index.html
// Needed for Carbon based applications which call into Cocoa
NSApplicationLoad();
// Must first call [[[NSWindow alloc] init] release] to get the NSWindow machinery set up so that NSCursor can use a window to cache the cursor image
[[[NSWindow alloc] init] release];
deleteAutoReleasePool();
inited = true;
}
}
static NSAutoreleasePool *sPool = NULL;
void createAutoReleasePool()
{
if(!sPool)
{
sPool = [[NSAutoreleasePool alloc] init];
}
}
void deleteAutoReleasePool()
{
if(sPool)
{
[sPool release];
sPool = NULL;
}
}

170
indra/llplugin/slplugin/slplugin.cpp Normal file → Executable file
View File

@@ -1,10 +1,12 @@
/**
/**
* @file slplugin.cpp
* @brief Loader shell for plugins, intended to be launched by the plugin host application, which directly loads a plugin dynamic library.
*
* @cond
*
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +14,13 @@
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
* online at http://secondlife.com/developers/opensource/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
* http://secondlife.com/developers/opensource/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
@@ -28,6 +30,9 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
*
* @endcond
*/
@@ -41,6 +46,7 @@
#if LL_DARWIN
#include <Carbon/Carbon.h>
#include "slplugin-objc.h"
#endif
#if LL_DARWIN || LL_LINUX
@@ -48,16 +54,17 @@
#endif
/*
On Mac OS, since we call WaitNextEvent, this process will show up in the dock unless we set the LSBackgroundOnly flag in the Info.plist.
On Mac OS, since we call WaitNextEvent, this process will show up in the dock unless we set the LSBackgroundOnly or LSUIElement flag in the Info.plist.
Normally non-bundled binaries don't have an info.plist file, but it's possible to embed one in the binary by adding this to the linker flags:
-sectcreate __TEXT __info_plist /path/to/slplugin_info.plist
which means adding this to the gcc flags:
-Wl,-sectcreate,__TEXT,__info_plist,/path/to/slplugin_info.plist
Now that SLPlugin is a bundled app on the Mac, this is no longer necessary (it can just use a regular Info.plist file), but I'm leaving this comment in for posterity.
*/
#if LL_DARWIN || LL_LINUX
@@ -68,7 +75,7 @@ static void crash_handler(int sig)
// TODO: add our own crash reporting
_exit(1);
}
#endif
#endif
#if LL_WINDOWS
#include <windows.h>
@@ -81,7 +88,7 @@ LONG WINAPI myWin32ExceptionHandler( struct _EXCEPTION_POINTERS* exception_infop
//std::cerr << "intercepted an unhandled exception and will exit immediately." << std::endl;
// TODO: replace exception handler before we exit?
return EXCEPTION_EXECUTE_HANDLER;
return EXCEPTION_EXECUTE_HANDLER;
}
// Taken from : http://blog.kalmbachnet.de/?postid=75
@@ -153,7 +160,7 @@ bool checkExceptionHandler()
if (prev_filter == NULL)
{
ok = FALSE;
if (myWin32ExceptionHandler == NULL)
if (NULL == myWin32ExceptionHandler)
{
LL_WARNS("AppInit") << "Exception handler uninitialized." << LL_ENDL;
}
@@ -167,7 +174,7 @@ bool checkExceptionHandler()
}
#endif
// If this application on Windows platform is a console application, a console is always
// If this application on Windows platform is a console application, a console is always
// created which is bad. Making it a Windows "application" via CMake settings but not
// adding any code to explicitly create windows does the right thing.
#if LL_WINDOWS
@@ -178,7 +185,7 @@ int main(int argc, char **argv)
{
ll_init_apr();
// Set up llerror logging
// Set up llerror logging
{
LLError::initForApplication(".");
LLError::setDefaultLevel(LLError::LEVEL_INFO);
@@ -191,25 +198,23 @@ int main(int argc, char **argv)
{
LL_ERRS("slplugin") << "usage: " << "SLPlugin" << " launcher_port" << LL_ENDL;
};
U32 port = 0;
if(!LLStringUtil::convertToU32(lpCmdLine, port))
{
LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL;
};
// Insert our exception handler into the system so this plugin doesn't
// Insert our exception handler into the system so this plugin doesn't
// display a crash message if something bad happens. The host app will
// see the missing heartbeat and log appropriately.
initExceptionHandler();
#elif LL_DARWIN || LL_LINUX
setpriority(PRIO_PROCESS, getpid(), 19);
if(argc < 2)
{
LL_ERRS("slplugin") << "usage: " << argv[0] << " launcher_port" << LL_ENDL;
}
U32 port = 0;
if(!LLStringUtil::convertToU32(argv[1], port))
{
@@ -227,23 +232,50 @@ int main(int argc, char **argv)
signal(SIGSYS, &crash_handler); // non-existent system call invoked
#endif
#if LL_DARWIN
setupCocoa();
createAutoReleasePool();
#endif
LLPluginProcessChild *plugin = new LLPluginProcessChild();
plugin->init(port);
#if LL_DARWIN
deleteAutoReleasePool();
#endif
LLTimer timer;
timer.start();
#if LL_WINDOWS
checkExceptionHandler();
#endif
#if LL_DARWIN
// If the plugin opens a new window (such as the Flash plugin's fullscreen player), we may need to bring this plugin process to the foreground.
// Use this to track the current frontmost window and bring this process to the front if it changes.
WindowRef front_window = NULL;
WindowGroupRef layer_group = NULL;
int window_hack_state = 0;
CreateWindowGroup(kWindowGroupAttrFixedLevel, &layer_group);
if(layer_group)
{
// Start out with a window layer that's way out in front (fixes the problem with the menubar not getting hidden on first switch to fullscreen youtube)
SetWindowGroupName(layer_group, CFSTR("SLPlugin Layer"));
SetWindowGroupLevel(layer_group, kCGOverlayWindowLevel);
}
#endif
#if LL_DARWIN
EventTargetRef event_target = GetEventDispatcherTarget();
#endif
while(!plugin->isDone())
{
timer.reset();
#if LL_DARWIN
createAutoReleasePool();
#endif
timer.reset();
plugin->idle();
#if LL_DARWIN
{
@@ -254,11 +286,85 @@ int main(int argc, char **argv)
SendEventToEventTarget (event, event_target);
ReleaseEvent(event);
}
// Check for a change in this process's frontmost window.
if(FrontWindow() != front_window)
{
ProcessSerialNumber self = { 0, kCurrentProcess };
ProcessSerialNumber parent = { 0, kNoProcess };
ProcessSerialNumber front = { 0, kNoProcess };
Boolean this_is_front_process = false;
Boolean parent_is_front_process = false;
{
// Get this process's parent
ProcessInfoRec info;
info.processInfoLength = sizeof(ProcessInfoRec);
info.processName = NULL;
info.processAppSpec = NULL;
if(GetProcessInformation( &self, &info ) == noErr)
{
parent = info.processLauncher;
}
// and figure out whether this process or its parent are currently frontmost
if(GetFrontProcess(&front) == noErr)
{
(void) SameProcess(&self, &front, &this_is_front_process);
(void) SameProcess(&parent, &front, &parent_is_front_process);
}
}
if((FrontWindow() != NULL) && (front_window == NULL))
{
// Opening the first window
if(window_hack_state == 0)
{
// Next time through the event loop, lower the window group layer
window_hack_state = 1;
}
if(layer_group)
{
SetWindowGroup(FrontWindow(), layer_group);
}
if(parent_is_front_process)
{
// Bring this process's windows to the front.
(void) SetFrontProcess( &self );
}
ActivateWindow(FrontWindow(), true);
}
else if((FrontWindow() == NULL) && (front_window != NULL))
{
// Closing the last window
if(this_is_front_process)
{
// Try to bring this process's parent to the front
(void) SetFrontProcess(&parent);
}
}
else if(window_hack_state == 1)
{
if(layer_group)
{
// Set the window group level back to something less extreme
SetWindowGroupLevel(layer_group, kCGNormalWindowLevel);
}
window_hack_state = 2;
}
front_window = FrontWindow();
}
}
#endif
F64 elapsed = timer.getElapsedTimeF64();
F64 remaining = plugin->getSleepTime() - elapsed;
if(remaining <= 0.0f)
{
// We've already used our full allotment.
@@ -271,26 +377,30 @@ int main(int argc, char **argv)
{
// LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, sleeping for " << remaining * 1000.0f << " ms" << LL_ENDL;
// timer.reset();
// timer.reset();
// This also services the network as needed.
plugin->sleep(remaining);
// LL_INFOS("slplugin") << "slept for "<< timer.getElapsedTimeF64() * 1000.0f << " ms" << LL_ENDL;
}
#if LL_WINDOWS
// More agressive checking of interfering exception handlers.
// Doesn't appear to be required so far - even for plugins
// that do crash with a single call to the intercept
// Doesn't appear to be required so far - even for plugins
// that do crash with a single call to the intercept
// exception handler such as QuickTime.
//checkExceptionHandler();
#endif
#if LL_DARWIN
deleteAutoReleasePool();
#endif
}
delete plugin;
ll_cleanup_apr();
ll_cleanup_apr();
return 0;
}

4
indra/llplugin/slplugin/slplugin_info.plist Normal file → Executable file
View File

@@ -6,7 +6,7 @@
<string>English</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>LSBackgroundOnly</key>
<true/>
<key>LSUIElement</key>
<string>1</string>
</dict>
</plist>