Imported existing code
This commit is contained in:
71
indra/llplugin/CMakeLists.txt
Normal file
71
indra/llplugin/CMakeLists.txt
Normal file
@@ -0,0 +1,71 @@
|
||||
# -*- cmake -*-
|
||||
|
||||
project(llplugin)
|
||||
|
||||
if (WORD_SIZE EQUAL 64)
|
||||
set(REQUIRE_PIC)
|
||||
endif(WORD_SIZE EQUAL 64)
|
||||
|
||||
include(00-Common)
|
||||
include(LLCommon)
|
||||
include(LLImage)
|
||||
include(LLMath)
|
||||
include(LLMessage)
|
||||
include(LLRender)
|
||||
include(LLXML)
|
||||
include(LLWindow)
|
||||
|
||||
include_directories(
|
||||
${LLCOMMON_INCLUDE_DIRS}
|
||||
${LLIMAGE_INCLUDE_DIRS}
|
||||
${LLMATH_INCLUDE_DIRS}
|
||||
${LLMESSAGE_INCLUDE_DIRS}
|
||||
${LLRENDER_INCLUDE_DIRS}
|
||||
${LLXML_INCLUDE_DIRS}
|
||||
${LLWINDOW_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
set(llplugin_SOURCE_FILES
|
||||
llpluginclassmedia.cpp
|
||||
llplugininstance.cpp
|
||||
llpluginmessage.cpp
|
||||
llpluginmessagepipe.cpp
|
||||
llpluginprocesschild.cpp
|
||||
llpluginprocessparent.cpp
|
||||
llpluginsharedmemory.cpp
|
||||
)
|
||||
|
||||
set(llplugin_HEADER_FILES
|
||||
CMakeLists.txt
|
||||
|
||||
llpluginclassmedia.h
|
||||
llpluginclassmediaowner.h
|
||||
llplugininstance.h
|
||||
llpluginmessage.h
|
||||
llpluginmessageclasses.h
|
||||
llpluginmessagepipe.h
|
||||
llpluginprocesschild.h
|
||||
llpluginprocessparent.h
|
||||
llpluginsharedmemory.h
|
||||
)
|
||||
|
||||
set_source_files_properties(${llplugin_HEADER_FILES}
|
||||
PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
|
||||
if(WORD_SIZE EQUAL 64)
|
||||
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)
|
||||
|
||||
list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES})
|
||||
|
||||
add_library (llplugin ${llplugin_SOURCE_FILES})
|
||||
|
||||
add_dependencies(llplugin
|
||||
prepare
|
||||
)
|
||||
|
||||
add_subdirectory(slplugin)
|
||||
1162
indra/llplugin/llpluginclassmedia.cpp
Normal file
1162
indra/llplugin/llpluginclassmedia.cpp
Normal file
File diff suppressed because it is too large
Load Diff
352
indra/llplugin/llpluginclassmedia.h
Normal file
352
indra/llplugin/llpluginclassmedia.h
Normal file
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* @file llpluginclassmedia.h
|
||||
* @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPLUGINCLASSMEDIA_H
|
||||
#define LL_LLPLUGINCLASSMEDIA_H
|
||||
|
||||
#include "llgltypes.h"
|
||||
#include "llpluginprocessparent.h"
|
||||
#include "llrect.h"
|
||||
#include "llpluginclassmediaowner.h"
|
||||
#include <queue>
|
||||
|
||||
|
||||
class LLPluginClassMedia : public LLPluginProcessParentOwner
|
||||
{
|
||||
LOG_CLASS(LLPluginClassMedia);
|
||||
public:
|
||||
LLPluginClassMedia(LLPluginClassMediaOwner *owner);
|
||||
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);
|
||||
|
||||
// undoes everything init() didm called by the media manager when destroying a source
|
||||
virtual void reset();
|
||||
|
||||
void idle(void);
|
||||
|
||||
// All of these may return 0 or an actual valid value.
|
||||
// Callers need to check the return for 0, and not use the values in that case.
|
||||
int getWidth() const { return (mMediaWidth > 0) ? mMediaWidth : 0; };
|
||||
int getHeight() const { return (mMediaHeight > 0) ? mMediaHeight : 0; };
|
||||
int getNaturalWidth() const { return mNaturalMediaWidth; };
|
||||
int getNaturalHeight() const { return mNaturalMediaHeight; };
|
||||
int getSetWidth() const { return mSetMediaWidth; };
|
||||
int getSetHeight() const { return mSetMediaHeight; };
|
||||
int getBitsWidth() const { return (mTextureWidth > 0) ? mTextureWidth : 0; };
|
||||
int getBitsHeight() const { return (mTextureHeight > 0) ? mTextureHeight : 0; };
|
||||
int getTextureWidth() const;
|
||||
int getTextureHeight() const;
|
||||
int getFullWidth() const { return mFullMediaWidth; };
|
||||
int getFullHeight() const { return mFullMediaHeight; };
|
||||
|
||||
// This may return NULL. Callers need to check for and handle this case.
|
||||
unsigned char* getBitsData();
|
||||
|
||||
// gets the format details of the texture data
|
||||
// These may return 0 if they haven't been set up yet. The caller needs to detect this case.
|
||||
int getTextureDepth() const { return mRequestedTextureDepth; };
|
||||
int getTextureFormatInternal() const { return mRequestedTextureInternalFormat; };
|
||||
int getTextureFormatPrimary() const { return mRequestedTextureFormat; };
|
||||
int getTextureFormatType() const { return mRequestedTextureType; };
|
||||
bool getTextureFormatSwapBytes() const { return mRequestedTextureSwapBytes; };
|
||||
bool getTextureCoordsOpenGL() const { return mRequestedTextureCoordsOpenGL; };
|
||||
|
||||
void setSize(int width, int height);
|
||||
void setAutoScale(bool auto_scale);
|
||||
|
||||
// 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
|
||||
// until you call idle() again.
|
||||
bool textureValid(void);
|
||||
|
||||
bool getDirty(LLRect *dirty_rect = NULL);
|
||||
void resetDirty(void);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
MOUSE_EVENT_DOWN,
|
||||
MOUSE_EVENT_UP,
|
||||
MOUSE_EVENT_MOVE,
|
||||
MOUSE_EVENT_DOUBLE_CLICK
|
||||
}EMouseEventType;
|
||||
|
||||
void mouseEvent(EMouseEventType type, int button, int x, int y, MASK modifiers);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
KEY_EVENT_DOWN,
|
||||
KEY_EVENT_UP,
|
||||
KEY_EVENT_REPEAT
|
||||
}EKeyEventType;
|
||||
|
||||
bool keyEvent(EKeyEventType type, int key_code, MASK modifiers);
|
||||
|
||||
void scrollEvent(int x, int y, MASK modifiers);
|
||||
|
||||
// Text may be unicode (utf8 encoded)
|
||||
bool textInput(const std::string &text, MASK modifiers);
|
||||
|
||||
void loadURI(const std::string &uri);
|
||||
|
||||
// "Loading" means uninitialized or any state prior to fully running (processing commands)
|
||||
bool isPluginLoading(void) { return mPlugin?mPlugin->isLoading():false; };
|
||||
|
||||
// "Running" means the steady state -- i.e. processing messages
|
||||
bool isPluginRunning(void) { return mPlugin?mPlugin->isRunning():false; };
|
||||
|
||||
// "Exited" means any regular or error state after "Running" (plugin may have crashed or exited normally)
|
||||
bool isPluginExited(void) { return mPlugin?mPlugin->isDone():false; };
|
||||
|
||||
std::string getPluginVersion() { return mPlugin?mPlugin->getPluginVersion():std::string(""); };
|
||||
|
||||
bool getDisableTimeout() { return mPlugin?mPlugin->getDisableTimeout():false; };
|
||||
void setDisableTimeout(bool disable) { if(mPlugin) mPlugin->setDisableTimeout(disable); };
|
||||
|
||||
// Inherited from LLPluginProcessParentOwner
|
||||
/* virtual */ void receivePluginMessage(const LLPluginMessage &message);
|
||||
/* virtual */ void pluginLaunchFailed();
|
||||
/* virtual */ void pluginDied();
|
||||
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PRIORITY_UNLOADED, // media plugin isn't even loaded.
|
||||
PRIORITY_STOPPED, // media is not playing, shouldn't need to update at all.
|
||||
PRIORITY_HIDDEN, // media is not being displayed or is out of view, don't need to do graphic updates, but may still update audio, playhead, etc.
|
||||
PRIORITY_SLIDESHOW, // media is in the far distance, updates very infrequently
|
||||
PRIORITY_LOW, // media is in the distance, may be rendered at reduced size
|
||||
PRIORITY_NORMAL, // normal (default) priority
|
||||
PRIORITY_HIGH // media has user focus and/or is taking up most of the screen
|
||||
}EPriority;
|
||||
|
||||
static const char* priorityToString(EPriority priority);
|
||||
void setPriority(EPriority priority);
|
||||
void setLowPrioritySizeLimit(int size);
|
||||
|
||||
F64 getCPUUsage();
|
||||
|
||||
// Valid after a MEDIA_EVENT_CURSOR_CHANGED event
|
||||
std::string getCursorName() const { return mCursorName; };
|
||||
|
||||
LLPluginClassMediaOwner::EMediaStatus getStatus() const { return mStatus; }
|
||||
|
||||
void cut();
|
||||
bool canCut() const { return mCanCut; };
|
||||
|
||||
void copy();
|
||||
bool canCopy() const { return mCanCopy; };
|
||||
|
||||
void paste();
|
||||
bool canPaste() const { return mCanPaste; };
|
||||
|
||||
///////////////////////////////////
|
||||
// media browser class functions
|
||||
bool pluginSupportsMediaBrowser(void);
|
||||
|
||||
void focus(bool focused);
|
||||
void clear_cache();
|
||||
void clear_cookies();
|
||||
void enable_cookies(bool enable);
|
||||
void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0);
|
||||
void browse_stop();
|
||||
void browse_reload(bool ignore_cache = false);
|
||||
void browse_forward();
|
||||
void browse_back();
|
||||
void set_status_redirect(int code, const std::string &url);
|
||||
void setBrowserUserAgent(const std::string& user_agent);
|
||||
|
||||
// This is valid after MEDIA_EVENT_NAVIGATE_BEGIN or MEDIA_EVENT_NAVIGATE_COMPLETE
|
||||
std::string getNavigateURI() const { return mNavigateURI; };
|
||||
|
||||
// These are valid after MEDIA_EVENT_NAVIGATE_COMPLETE
|
||||
S32 getNavigateResultCode() const { return mNavigateResultCode; };
|
||||
std::string getNavigateResultString() const { return mNavigateResultString; };
|
||||
bool getHistoryBackAvailable() const { return mHistoryBackAvailable; };
|
||||
bool getHistoryForwardAvailable() const { return mHistoryForwardAvailable; };
|
||||
|
||||
// This is valid after MEDIA_EVENT_PROGRESS_UPDATED
|
||||
int getProgressPercent() const { return mProgressPercent; };
|
||||
|
||||
// This is valid after MEDIA_EVENT_STATUS_TEXT_CHANGED
|
||||
std::string getStatusText() const { return mStatusText; };
|
||||
|
||||
// This is valid after MEDIA_EVENT_LOCATION_CHANGED
|
||||
std::string getLocation() const { return mLocation; };
|
||||
|
||||
// This is valid after MEDIA_EVENT_CLICK_LINK_HREF or MEDIA_EVENT_CLICK_LINK_NOFOLLOW
|
||||
std::string getClickURL() const { return mClickURL; };
|
||||
|
||||
// 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; };
|
||||
|
||||
// Crash the plugin. If you use this outside of a testbed, you will be punished.
|
||||
void crashPlugin();
|
||||
|
||||
// Hang the plugin. If you use this outside of a testbed, you will be punished.
|
||||
void hangPlugin();
|
||||
|
||||
///////////////////////////////////
|
||||
// media time class functions
|
||||
bool pluginSupportsMediaTime(void);
|
||||
void stop();
|
||||
void start(float rate = 0.0f);
|
||||
void pause();
|
||||
void seek(float time);
|
||||
void setLoop(bool loop);
|
||||
void setVolume(float volume);
|
||||
float getVolume();
|
||||
|
||||
F64 getCurrentTime(void) const { return mCurrentTime; };
|
||||
F64 getDuration(void) const { return mDuration; };
|
||||
F64 getCurrentPlayRate(void) { return mCurrentRate; };
|
||||
F64 getLoadedDuration(void) const { return mLoadedDuration; };
|
||||
|
||||
// Initialize the URL history of the plugin by sending
|
||||
// "init_history" message
|
||||
void initializeUrlHistory(const LLSD& url_history);
|
||||
|
||||
protected:
|
||||
|
||||
LLPluginClassMediaOwner *mOwner;
|
||||
|
||||
// Notify this object's owner that an event has occurred.
|
||||
void mediaEvent(LLPluginClassMediaOwner::EMediaEvent event);
|
||||
|
||||
void sendMessage(const LLPluginMessage &message); // Send message internally, either queueing or sending directly.
|
||||
std::queue<LLPluginMessage> mSendQueue; // Used to queue messages while the plugin initializes.
|
||||
|
||||
void setSizeInternal(void);
|
||||
|
||||
bool mTextureParamsReceived; // the mRequestedTexture* fields are only valid when this is true
|
||||
S32 mRequestedTextureDepth;
|
||||
LLGLenum mRequestedTextureInternalFormat;
|
||||
LLGLenum mRequestedTextureFormat;
|
||||
LLGLenum mRequestedTextureType;
|
||||
bool mRequestedTextureSwapBytes;
|
||||
bool mRequestedTextureCoordsOpenGL;
|
||||
|
||||
std::string mTextureSharedMemoryName;
|
||||
size_t mTextureSharedMemorySize;
|
||||
|
||||
// True to scale requested media up to the full size of the texture (i.e. next power of two)
|
||||
bool mAutoScaleMedia;
|
||||
|
||||
// default media size for the plugin, from the texture_params message.
|
||||
int mDefaultMediaWidth;
|
||||
int mDefaultMediaHeight;
|
||||
|
||||
// Size that has been requested by the plugin itself
|
||||
int mNaturalMediaWidth;
|
||||
int mNaturalMediaHeight;
|
||||
|
||||
// Size that has been requested with setSize()
|
||||
int mSetMediaWidth;
|
||||
int mSetMediaHeight;
|
||||
|
||||
// Full calculated media size (before auto-scale and downsample calculations)
|
||||
int mFullMediaWidth;
|
||||
int mFullMediaHeight;
|
||||
|
||||
// Actual media size being set (after auto-scale)
|
||||
int mRequestedMediaWidth;
|
||||
int mRequestedMediaHeight;
|
||||
|
||||
// Texture size calculated from actual media size
|
||||
int mRequestedTextureWidth;
|
||||
int mRequestedTextureHeight;
|
||||
|
||||
// Size that the plugin has acknowledged
|
||||
int mTextureWidth;
|
||||
int mTextureHeight;
|
||||
int mMediaWidth;
|
||||
int mMediaHeight;
|
||||
|
||||
float mRequestedVolume;
|
||||
|
||||
// Priority of this media stream
|
||||
EPriority mPriority;
|
||||
int mLowPrioritySizeLimit;
|
||||
|
||||
bool mAllowDownsample;
|
||||
int mPadding;
|
||||
|
||||
|
||||
LLPluginProcessParent *mPlugin;
|
||||
|
||||
LLRect mDirtyRect;
|
||||
|
||||
std::string translateModifiers(MASK modifiers);
|
||||
|
||||
std::string mCursorName;
|
||||
int mLastMouseX;
|
||||
int mLastMouseY;
|
||||
|
||||
LLPluginClassMediaOwner::EMediaStatus mStatus;
|
||||
|
||||
F64 mSleepTime;
|
||||
|
||||
bool mCanCut;
|
||||
bool mCanCopy;
|
||||
bool mCanPaste;
|
||||
|
||||
std::string mMediaName;
|
||||
std::string mMediaDescription;
|
||||
|
||||
/////////////////////////////////////////
|
||||
// media_browser class
|
||||
std::string mNavigateURI;
|
||||
S32 mNavigateResultCode;
|
||||
std::string mNavigateResultString;
|
||||
bool mHistoryBackAvailable;
|
||||
bool mHistoryForwardAvailable;
|
||||
std::string mStatusText;
|
||||
int mProgressPercent;
|
||||
std::string mLocation;
|
||||
std::string mClickURL;
|
||||
std::string mClickTarget;
|
||||
|
||||
/////////////////////////////////////////
|
||||
// media_time class
|
||||
F64 mCurrentTime;
|
||||
F64 mDuration;
|
||||
F64 mCurrentRate;
|
||||
F64 mLoadedDuration;
|
||||
|
||||
};
|
||||
|
||||
#endif // LL_LLPLUGINCLASSMEDIA_H
|
||||
82
indra/llplugin/llpluginclassmediaowner.h
Normal file
82
indra/llplugin/llpluginclassmediaowner.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @file llpluginclassmediaowner.h
|
||||
* @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPLUGINCLASSMEDIAOWNER_H
|
||||
#define LL_LLPLUGINCLASSMEDIAOWNER_H
|
||||
|
||||
#include "llpluginprocessparent.h"
|
||||
#include "llrect.h"
|
||||
#include <queue>
|
||||
|
||||
class LLPluginClassMedia;
|
||||
|
||||
class LLPluginClassMediaOwner
|
||||
{
|
||||
public:
|
||||
typedef enum
|
||||
{
|
||||
MEDIA_EVENT_CONTENT_UPDATED, // contents/dirty rect have updated
|
||||
MEDIA_EVENT_TIME_DURATION_UPDATED, // current time and/or duration have updated
|
||||
MEDIA_EVENT_SIZE_CHANGED, // media size has changed
|
||||
MEDIA_EVENT_CURSOR_CHANGED, // plugin has requested a cursor change
|
||||
|
||||
MEDIA_EVENT_NAVIGATE_BEGIN, // browser has begun navigation
|
||||
MEDIA_EVENT_NAVIGATE_COMPLETE, // browser has finished navigation
|
||||
MEDIA_EVENT_PROGRESS_UPDATED, // browser has updated loading progress
|
||||
MEDIA_EVENT_STATUS_TEXT_CHANGED, // browser has updated the status text
|
||||
MEDIA_EVENT_NAME_CHANGED, // browser has updated the name of the media (typically <title> tag)
|
||||
MEDIA_EVENT_LOCATION_CHANGED, // browser location (URL) has changed (maybe due to internal navagation/frames/etc)
|
||||
MEDIA_EVENT_CLICK_LINK_HREF, // I'm not entirely sure what the semantics of these two are
|
||||
MEDIA_EVENT_CLICK_LINK_NOFOLLOW,
|
||||
|
||||
MEDIA_EVENT_PLUGIN_FAILED_LAUNCH, // The plugin failed to launch
|
||||
MEDIA_EVENT_PLUGIN_FAILED // The plugin died unexpectedly
|
||||
|
||||
} EMediaEvent;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
MEDIA_NONE, // Uninitialized -- no useful state
|
||||
MEDIA_LOADING, // loading or navigating
|
||||
MEDIA_LOADED, // navigation/preroll complete
|
||||
MEDIA_ERROR, // navigation/preroll failed
|
||||
MEDIA_PLAYING, // playing (only for time-based media)
|
||||
MEDIA_PAUSED, // paused (only for time-based media)
|
||||
MEDIA_DONE // finished playing (only for time-based media)
|
||||
|
||||
} EMediaStatus;
|
||||
|
||||
virtual ~LLPluginClassMediaOwner() {};
|
||||
virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {};
|
||||
};
|
||||
|
||||
#endif // LL_LLPLUGINCLASSMEDIAOWNER_H
|
||||
172
indra/llplugin/llplugininstance.cpp
Normal file
172
indra/llplugin/llplugininstance.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* @file llplugininstance.cpp
|
||||
* @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llplugininstance.h"
|
||||
|
||||
#include "llapr.h"
|
||||
|
||||
/** Virtual destructor. */
|
||||
LLPluginInstanceMessageListener::~LLPluginInstanceMessageListener()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO:DOC describe how it's used
|
||||
*/
|
||||
const char *LLPluginInstance::PLUGIN_INIT_FUNCTION_NAME = "LLPluginInitEntryPoint";
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param[in] owner Plugin instance. TODO:DOC is this a good description of what "owner" is?
|
||||
*/
|
||||
LLPluginInstance::LLPluginInstance(LLPluginInstanceMessageListener *owner) :
|
||||
mDSOHandle(NULL),
|
||||
mPluginUserData(NULL),
|
||||
mPluginSendMessageFunction(NULL)
|
||||
{
|
||||
mOwner = owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
LLPluginInstance::~LLPluginInstance()
|
||||
{
|
||||
if(mDSOHandle != NULL)
|
||||
{
|
||||
apr_dso_unload(mDSOHandle);
|
||||
mDSOHandle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically loads the plugin and runs the plugin's init function.
|
||||
*
|
||||
* @param[in] plugin_file Name of plugin dll/dylib/so. TODO:DOC is this correct? see .h
|
||||
* @return 0 if successful, APR error code or error code from the plugin's init function on failure.
|
||||
*/
|
||||
int LLPluginInstance::load(std::string &plugin_file)
|
||||
{
|
||||
pluginInitFunction init_function = NULL;
|
||||
|
||||
int result = apr_dso_load(&mDSOHandle,
|
||||
plugin_file.c_str(),
|
||||
gAPRPoolp);
|
||||
if(result != APR_SUCCESS)
|
||||
{
|
||||
char buf[1024];
|
||||
apr_dso_error(mDSOHandle, buf, sizeof(buf));
|
||||
|
||||
LL_WARNS("Plugin") << "apr_dso_load of " << plugin_file << " failed with error " << result << " , additional info string: " << buf << LL_ENDL;
|
||||
|
||||
}
|
||||
|
||||
if(result == APR_SUCCESS)
|
||||
{
|
||||
result = apr_dso_sym((apr_dso_handle_sym_t*)&init_function,
|
||||
mDSOHandle,
|
||||
PLUGIN_INIT_FUNCTION_NAME);
|
||||
|
||||
if(result != APR_SUCCESS)
|
||||
{
|
||||
LL_WARNS("Plugin") << "apr_dso_sym failed with error " << result << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
if(result == APR_SUCCESS)
|
||||
{
|
||||
result = init_function(staticReceiveMessage, (void*)this, &mPluginSendMessageFunction, &mPluginUserData);
|
||||
|
||||
if(result != APR_SUCCESS)
|
||||
{
|
||||
LL_WARNS("Plugin") << "call to init function failed with error " << result << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the plugin.
|
||||
*
|
||||
* @param[in] message Message
|
||||
*/
|
||||
void LLPluginInstance::sendMessage(const std::string &message)
|
||||
{
|
||||
if(mPluginSendMessageFunction)
|
||||
{
|
||||
LL_DEBUGS("Plugin") << "sending message to plugin: \"" << message << "\"" << LL_ENDL;
|
||||
mPluginSendMessageFunction(message.c_str(), &mPluginUserData);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "dropping message: \"" << message << "\"" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Idle. TODO:DOC what's the purpose of this?
|
||||
*
|
||||
*/
|
||||
void LLPluginInstance::idle(void)
|
||||
{
|
||||
}
|
||||
|
||||
// static
|
||||
void LLPluginInstance::staticReceiveMessage(const char *message_string, void **user_data)
|
||||
{
|
||||
// TODO: validate that the user_data argument is still a valid LLPluginInstance pointer
|
||||
// we could also use a key that's looked up in a map (instead of a direct pointer) for safety, but that's probably overkill
|
||||
LLPluginInstance *self = (LLPluginInstance*)*user_data;
|
||||
self->receiveMessage(message_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin receives message from plugin loader shell.
|
||||
*
|
||||
* @param[in] message_string Message
|
||||
*/
|
||||
void LLPluginInstance::receiveMessage(const char *message_string)
|
||||
{
|
||||
if(mOwner)
|
||||
{
|
||||
LL_DEBUGS("Plugin") << "processing incoming message: \"" << message_string << "\"" << LL_ENDL;
|
||||
mOwner->receivePluginMessage(message_string);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "dropping incoming message: \"" << message_string << "\"" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
104
indra/llplugin/llplugininstance.h
Normal file
104
indra/llplugin/llplugininstance.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* @file llplugininstance.h
|
||||
* @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPLUGININSTANCE_H
|
||||
#define LL_LLPLUGININSTANCE_H
|
||||
|
||||
#include "llstring.h"
|
||||
#include "llapr.h"
|
||||
|
||||
#include "apr_dso.h"
|
||||
|
||||
/**
|
||||
* @brief LLPluginInstanceMessageListener receives messages sent from the plugin loader shell to the plugin.
|
||||
*/
|
||||
class LLPluginInstanceMessageListener
|
||||
{
|
||||
public:
|
||||
virtual ~LLPluginInstanceMessageListener();
|
||||
/** Plugin receives message from plugin loader shell. */
|
||||
virtual void receivePluginMessage(const std::string &message) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
|
||||
*/
|
||||
class LLPluginInstance
|
||||
{
|
||||
LOG_CLASS(LLPluginInstance);
|
||||
public:
|
||||
LLPluginInstance(LLPluginInstanceMessageListener *owner);
|
||||
virtual ~LLPluginInstance();
|
||||
|
||||
// Load a plugin dll/dylib/so
|
||||
// Returns 0 if successful, APR error code or error code returned from the plugin's init function on failure.
|
||||
int load(std::string &plugin_file);
|
||||
|
||||
// Sends a message to the plugin.
|
||||
void sendMessage(const std::string &message);
|
||||
|
||||
// TODO:DOC is this comment obsolete? can't find "send_count" anywhere in indra tree.
|
||||
// send_count is the maximum number of message to process from the send queue. If negative, it will drain the queue completely.
|
||||
// The receive queue is always drained completely.
|
||||
// Returns the total number of messages processed from both queues.
|
||||
void idle(void);
|
||||
|
||||
/** The signature of the function for sending a message from plugin to plugin loader shell.
|
||||
*
|
||||
* @param[in] message_string Null-terminated C string
|
||||
* @param[in] user_data The opaque reference that the callee supplied during setup.
|
||||
*/
|
||||
typedef void (*sendMessageFunction) (const char *message_string, void **user_data);
|
||||
|
||||
/** The signature of the plugin init function. TODO:DOC check direction (pluging loader shell to plugin?)
|
||||
*
|
||||
* @param[in] host_user_data Data from plugin loader shell.
|
||||
* @param[in] plugin_send_function Function for sending from the plugin loader shell to plugin.
|
||||
*/
|
||||
typedef int (*pluginInitFunction) (sendMessageFunction host_send_func, void *host_user_data, sendMessageFunction *plugin_send_func, void **plugin_user_data);
|
||||
|
||||
/** Name of plugin init function */
|
||||
static const char *PLUGIN_INIT_FUNCTION_NAME;
|
||||
|
||||
private:
|
||||
static void staticReceiveMessage(const char *message_string, void **user_data);
|
||||
void receiveMessage(const char *message_string);
|
||||
|
||||
apr_dso_handle_t *mDSOHandle;
|
||||
|
||||
void *mPluginUserData;
|
||||
sendMessageFunction mPluginSendMessageFunction;
|
||||
|
||||
LLPluginInstanceMessageListener *mOwner;
|
||||
};
|
||||
|
||||
#endif // LL_LLPLUGININSTANCE_H
|
||||
442
indra/llplugin/llpluginmessage.cpp
Normal file
442
indra/llplugin/llpluginmessage.cpp
Normal file
@@ -0,0 +1,442 @@
|
||||
/**
|
||||
* @file llpluginmessage.cpp
|
||||
* @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llpluginmessage.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "u64.h"
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
LLPluginMessage::LLPluginMessage()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param[in] p Existing message
|
||||
*/
|
||||
LLPluginMessage::LLPluginMessage(const LLPluginMessage &p)
|
||||
{
|
||||
mMessage = p.mMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param[in] message_class Message class
|
||||
* @param[in] message_name Message name
|
||||
*/
|
||||
LLPluginMessage::LLPluginMessage(const std::string &message_class, const std::string &message_name)
|
||||
{
|
||||
setMessage(message_class, message_name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
LLPluginMessage::~LLPluginMessage()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all internal state.
|
||||
*/
|
||||
void LLPluginMessage::clear()
|
||||
{
|
||||
mMessage = LLSD::emptyMap();
|
||||
mMessage["params"] = LLSD::emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message class and name. Also has the side-effect of clearing any key-value pairs in the message.
|
||||
*
|
||||
* @param[in] message_class Message class
|
||||
* @param[in] message_name Message name
|
||||
*/
|
||||
void LLPluginMessage::setMessage(const std::string &message_class, const std::string &message_name)
|
||||
{
|
||||
clear();
|
||||
mMessage["class"] = message_class;
|
||||
mMessage["name"] = message_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key/value pair in the message, where the value is a string.
|
||||
*
|
||||
* @param[in] key Key
|
||||
* @param[in] value String value
|
||||
*/
|
||||
void LLPluginMessage::setValue(const std::string &key, const std::string &value)
|
||||
{
|
||||
mMessage["params"][key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key/value pair in the message, where the value is LLSD.
|
||||
*
|
||||
* @param[in] key Key
|
||||
* @param[in] value LLSD value
|
||||
*/
|
||||
void LLPluginMessage::setValueLLSD(const std::string &key, const LLSD &value)
|
||||
{
|
||||
mMessage["params"][key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key/value pair in the message, where the value is signed 32-bit.
|
||||
*
|
||||
* @param[in] key Key
|
||||
* @param[in] value 32-bit signed value
|
||||
*/
|
||||
void LLPluginMessage::setValueS32(const std::string &key, S32 value)
|
||||
{
|
||||
mMessage["params"][key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key/value pair in the message, where the value is unsigned 32-bit. The value is stored as a string beginning with "0x".
|
||||
*
|
||||
* @param[in] key Key
|
||||
* @param[in] value 32-bit unsigned value
|
||||
*/
|
||||
void LLPluginMessage::setValueU32(const std::string &key, U32 value)
|
||||
{
|
||||
std::stringstream temp;
|
||||
temp << "0x" << std::hex << value;
|
||||
setValue(key, temp.str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key/value pair in the message, where the value is a bool.
|
||||
*
|
||||
* @param[in] key Key
|
||||
* @param[in] value Boolean value
|
||||
*/
|
||||
void LLPluginMessage::setValueBoolean(const std::string &key, bool value)
|
||||
{
|
||||
mMessage["params"][key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key/value pair in the message, where the value is a double.
|
||||
*
|
||||
* @param[in] key Key
|
||||
* @param[in] value Boolean value
|
||||
*/
|
||||
void LLPluginMessage::setValueReal(const std::string &key, F64 value)
|
||||
{
|
||||
mMessage["params"][key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key/value pair in the message, where the value is a pointer. The pointer is stored as a string.
|
||||
*
|
||||
* @param[in] key Key
|
||||
* @param[in] value Pointer value
|
||||
*/
|
||||
void LLPluginMessage::setValuePointer(const std::string &key, void* value)
|
||||
{
|
||||
std::stringstream temp;
|
||||
// iostreams should output pointer values in hex with an initial 0x by default.
|
||||
temp << value;
|
||||
setValue(key, temp.str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message class.
|
||||
*
|
||||
* @return Message class
|
||||
*/
|
||||
std::string LLPluginMessage::getClass(void) const
|
||||
{
|
||||
return mMessage["class"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message name.
|
||||
*
|
||||
* @return Message name
|
||||
*/
|
||||
std::string LLPluginMessage::getName(void) const
|
||||
{
|
||||
return mMessage["name"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified key exists in this message (useful for optional parameters).
|
||||
*
|
||||
* @param[in] key Key
|
||||
*
|
||||
* @return True if key exists, false otherwise.
|
||||
*/
|
||||
bool LLPluginMessage::hasValue(const std::string &key) const
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(mMessage["params"].has(key))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a key as a string. If the key does not exist, an empty string will be returned.
|
||||
*
|
||||
* @param[in] key Key
|
||||
*
|
||||
* @return String value of key if key exists, empty string if key does not exist.
|
||||
*/
|
||||
std::string LLPluginMessage::getValue(const std::string &key) const
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if(mMessage["params"].has(key))
|
||||
{
|
||||
result = mMessage["params"][key].asString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a key as LLSD. If the key does not exist, a null LLSD will be returned.
|
||||
*
|
||||
* @param[in] key Key
|
||||
*
|
||||
* @return LLSD value of key if key exists, null LLSD if key does not exist.
|
||||
*/
|
||||
LLSD LLPluginMessage::getValueLLSD(const std::string &key) const
|
||||
{
|
||||
LLSD result;
|
||||
|
||||
if(mMessage["params"].has(key))
|
||||
{
|
||||
result = mMessage["params"][key];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a key as signed 32-bit int. If the key does not exist, 0 will be returned.
|
||||
*
|
||||
* @param[in] key Key
|
||||
*
|
||||
* @return Signed 32-bit int value of key if key exists, 0 if key does not exist.
|
||||
*/
|
||||
S32 LLPluginMessage::getValueS32(const std::string &key) const
|
||||
{
|
||||
S32 result = 0;
|
||||
|
||||
if(mMessage["params"].has(key))
|
||||
{
|
||||
result = mMessage["params"][key].asInteger();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a key as unsigned 32-bit int. If the key does not exist, 0 will be returned.
|
||||
*
|
||||
* @param[in] key Key
|
||||
*
|
||||
* @return Unsigned 32-bit int value of key if key exists, 0 if key does not exist.
|
||||
*/
|
||||
U32 LLPluginMessage::getValueU32(const std::string &key) const
|
||||
{
|
||||
U32 result = 0;
|
||||
|
||||
if(mMessage["params"].has(key))
|
||||
{
|
||||
std::string value = mMessage["params"][key].asString();
|
||||
|
||||
result = (U32)strtoul(value.c_str(), NULL, 16);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a key as a bool. If the key does not exist, false will be returned.
|
||||
*
|
||||
* @param[in] key Key
|
||||
*
|
||||
* @return Boolean value of key if it exists, false otherwise.
|
||||
*/
|
||||
bool LLPluginMessage::getValueBoolean(const std::string &key) const
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(mMessage["params"].has(key))
|
||||
{
|
||||
result = mMessage["params"][key].asBoolean();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a key as a double. If the key does not exist, 0 will be returned.
|
||||
*
|
||||
* @param[in] key Key
|
||||
*
|
||||
* @return Value as a double if key exists, 0 otherwise.
|
||||
*/
|
||||
F64 LLPluginMessage::getValueReal(const std::string &key) const
|
||||
{
|
||||
F64 result = 0.0f;
|
||||
|
||||
if(mMessage["params"].has(key))
|
||||
{
|
||||
result = mMessage["params"][key].asReal();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a key as a pointer. If the key does not exist, NULL will be returned.
|
||||
*
|
||||
* @param[in] key Key
|
||||
*
|
||||
* @return Pointer value if key exists, NULL otherwise.
|
||||
*/
|
||||
void* LLPluginMessage::getValuePointer(const std::string &key) const
|
||||
{
|
||||
void* result = NULL;
|
||||
|
||||
if(mMessage["params"].has(key))
|
||||
{
|
||||
std::string value = mMessage["params"][key].asString();
|
||||
|
||||
result = (void*)llstrtou64(value.c_str(), NULL, 16);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten the message into a string.
|
||||
*
|
||||
* @return Message as a string.
|
||||
*/
|
||||
std::string LLPluginMessage::generate(void) const
|
||||
{
|
||||
std::ostringstream result;
|
||||
|
||||
// Pretty XML may be slightly easier to deal with while debugging...
|
||||
// LLSDSerialize::toXML(mMessage, result);
|
||||
LLSDSerialize::toPrettyXML(mMessage, result);
|
||||
|
||||
return result.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an incoming message into component parts. Clears all existing state before starting the parse.
|
||||
*
|
||||
* @return Returns -1 on failure, otherwise returns the number of key/value pairs in the incoming message.
|
||||
*/
|
||||
int LLPluginMessage::parse(const std::string &message)
|
||||
{
|
||||
// clear any previous state
|
||||
clear();
|
||||
|
||||
std::istringstream input(message);
|
||||
|
||||
S32 parse_result = LLSDSerialize::fromXML(mMessage, input);
|
||||
|
||||
return (int)parse_result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
LLPluginMessageListener::~LLPluginMessageListener()
|
||||
{
|
||||
// TODO: should listeners have a way to ensure they're removed from dispatcher lists when deleted?
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
LLPluginMessageDispatcher::~LLPluginMessageDispatcher()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message listener. TODO:DOC need more info on what uses this. when are multiple listeners needed?
|
||||
*
|
||||
* @param[in] listener Message listener
|
||||
*/
|
||||
void LLPluginMessageDispatcher::addPluginMessageListener(LLPluginMessageListener *listener)
|
||||
{
|
||||
mListeners.insert(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a message listener.
|
||||
*
|
||||
* @param[in] listener Message listener
|
||||
*/
|
||||
void LLPluginMessageDispatcher::removePluginMessageListener(LLPluginMessageListener *listener)
|
||||
{
|
||||
mListeners.erase(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Distribute a message to all message listeners.
|
||||
*
|
||||
* @param[in] message Message
|
||||
*/
|
||||
void LLPluginMessageDispatcher::dispatchPluginMessage(const LLPluginMessage &message)
|
||||
{
|
||||
for (listener_set_t::iterator it = mListeners.begin();
|
||||
it != mListeners.end();
|
||||
)
|
||||
{
|
||||
LLPluginMessageListener* listener = *it;
|
||||
listener->receivePluginMessage(message);
|
||||
// In case something deleted an entry.
|
||||
it = mListeners.upper_bound(listener);
|
||||
}
|
||||
}
|
||||
142
indra/llplugin/llpluginmessage.h
Normal file
142
indra/llplugin/llpluginmessage.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* @file llpluginmessage.h
|
||||
* @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPLUGINMESSAGE_H
|
||||
#define LL_LLPLUGINMESSAGE_H
|
||||
|
||||
#include "llsd.h"
|
||||
|
||||
/**
|
||||
* @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
|
||||
*/
|
||||
class LLPluginMessage
|
||||
{
|
||||
LOG_CLASS(LLPluginMessage);
|
||||
public:
|
||||
LLPluginMessage();
|
||||
LLPluginMessage(const LLPluginMessage &p);
|
||||
LLPluginMessage(const std::string &message_class, const std::string &message_name);
|
||||
~LLPluginMessage();
|
||||
|
||||
// reset all internal state
|
||||
void clear(void);
|
||||
|
||||
// Sets the message class and name
|
||||
// Also has the side-effect of clearing any key/value pairs in the message.
|
||||
void setMessage(const std::string &message_class, const std::string &message_name);
|
||||
|
||||
// Sets a key/value pair in the message
|
||||
void setValue(const std::string &key, const std::string &value);
|
||||
void setValueLLSD(const std::string &key, const LLSD &value);
|
||||
void setValueS32(const std::string &key, S32 value);
|
||||
void setValueU32(const std::string &key, U32 value);
|
||||
void setValueBoolean(const std::string &key, bool value);
|
||||
void setValueReal(const std::string &key, F64 value);
|
||||
void setValuePointer(const std::string &key, void *value);
|
||||
|
||||
std::string getClass(void) const;
|
||||
std::string getName(void) const;
|
||||
|
||||
// Returns true if the specified key exists in this message (useful for optional parameters)
|
||||
bool hasValue(const std::string &key) const;
|
||||
|
||||
// get the value of a particular key as a string. If the key doesn't exist in the message, an empty string will be returned.
|
||||
std::string getValue(const std::string &key) const;
|
||||
|
||||
// get the value of a particular key as LLSD. If the key doesn't exist in the message, a null LLSD will be returned.
|
||||
LLSD getValueLLSD(const std::string &key) const;
|
||||
|
||||
// get the value of a key as a S32. If the value wasn't set as a S32, behavior is undefined.
|
||||
S32 getValueS32(const std::string &key) const;
|
||||
|
||||
// get the value of a key as a U32. Since there isn't an LLSD type for this, we use a hexadecimal string instead.
|
||||
U32 getValueU32(const std::string &key) const;
|
||||
|
||||
// get the value of a key as a Boolean.
|
||||
bool getValueBoolean(const std::string &key) const;
|
||||
|
||||
// get the value of a key as a float.
|
||||
F64 getValueReal(const std::string &key) const;
|
||||
|
||||
// get the value of a key as a pointer.
|
||||
void* getValuePointer(const std::string &key) const;
|
||||
|
||||
// Flatten the message into a string
|
||||
std::string generate(void) const;
|
||||
|
||||
// Parse an incoming message into component parts
|
||||
// (this clears out all existing state before starting the parse)
|
||||
// Returns -1 on failure, otherwise returns the number of key/value pairs in the message.
|
||||
int parse(const std::string &message);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
LLSD mMessage;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Listener for plugin messages.
|
||||
*/
|
||||
class LLPluginMessageListener
|
||||
{
|
||||
public:
|
||||
virtual ~LLPluginMessageListener();
|
||||
/** Plugin receives message from plugin loader shell. */
|
||||
virtual void receivePluginMessage(const LLPluginMessage &message) = 0;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Dispatcher for plugin messages.
|
||||
*
|
||||
* Manages the set of plugin message listeners and distributes messages to plugin message listeners.
|
||||
*/
|
||||
class LLPluginMessageDispatcher
|
||||
{
|
||||
public:
|
||||
virtual ~LLPluginMessageDispatcher();
|
||||
|
||||
void addPluginMessageListener(LLPluginMessageListener *);
|
||||
void removePluginMessageListener(LLPluginMessageListener *);
|
||||
protected:
|
||||
void dispatchPluginMessage(const LLPluginMessage &message);
|
||||
|
||||
/** A set of message listeners. */
|
||||
typedef std::set<LLPluginMessageListener*> listener_set_t;
|
||||
/** The set of message listeners. */
|
||||
listener_set_t mListeners;
|
||||
};
|
||||
|
||||
|
||||
#endif // LL_LLPLUGINMESSAGE_H
|
||||
57
indra/llplugin/llpluginmessageclasses.h
Normal file
57
indra/llplugin/llpluginmessageclasses.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @file llpluginmessageclasses.h
|
||||
* @brief This file defines the versions of existing message classes for LLPluginMessage.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPLUGINMESSAGECLASSES_H
|
||||
#define LL_LLPLUGINMESSAGECLASSES_H
|
||||
|
||||
// Version strings for each plugin message class.
|
||||
// Backwards-compatible changes (i.e. changes which only add new messges) should increment the minor version (i.e. "1.0" -> "1.1").
|
||||
// Non-backwards-compatible changes (which delete messages or change their semantics) should increment the major version (i.e. "1.1" -> "2.0").
|
||||
// Plugins will supply the set of message classes they understand, with version numbers, as part of their init_response message.
|
||||
// The contents and semantics of the base:init message must NEVER change in a non-backwards-compatible way, as a special case.
|
||||
|
||||
#define LLPLUGIN_MESSAGE_CLASS_INTERNAL "internal"
|
||||
#define LLPLUGIN_MESSAGE_CLASS_INTERNAL_VERSION "1.0"
|
||||
|
||||
#define LLPLUGIN_MESSAGE_CLASS_BASE "base"
|
||||
#define LLPLUGIN_MESSAGE_CLASS_BASE_VERSION "1.0"
|
||||
|
||||
#define LLPLUGIN_MESSAGE_CLASS_MEDIA "media"
|
||||
#define LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION "1.0"
|
||||
|
||||
#define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER "media_browser"
|
||||
#define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION "1.0"
|
||||
|
||||
#define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME "media_time"
|
||||
#define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION "1.0"
|
||||
|
||||
#endif // LL_LLPLUGINMESSAGECLASSES_H
|
||||
316
indra/llplugin/llpluginmessagepipe.cpp
Normal file
316
indra/llplugin/llpluginmessagepipe.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* @file llpluginmessagepipe.cpp
|
||||
* @brief Classes that implement connections from the plugin system to pipes/pumps.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llbufferstream.h"
|
||||
|
||||
#include "llapr.h"
|
||||
|
||||
static const char MESSAGE_DELIMITER = '\0';
|
||||
|
||||
LLPluginMessagePipeOwner::LLPluginMessagePipeOwner() :
|
||||
mMessagePipe(NULL),
|
||||
mSocketError(APR_SUCCESS)
|
||||
{
|
||||
}
|
||||
|
||||
// virtual
|
||||
LLPluginMessagePipeOwner::~LLPluginMessagePipeOwner()
|
||||
{
|
||||
killMessagePipe();
|
||||
}
|
||||
|
||||
// virtual
|
||||
apr_status_t LLPluginMessagePipeOwner::socketError(apr_status_t error)
|
||||
{
|
||||
mSocketError = error;
|
||||
return error;
|
||||
};
|
||||
|
||||
//virtual
|
||||
void LLPluginMessagePipeOwner::setMessagePipe(LLPluginMessagePipe *read_pipe)
|
||||
{
|
||||
// Save a reference to this pipe
|
||||
mMessagePipe = read_pipe;
|
||||
}
|
||||
|
||||
bool LLPluginMessagePipeOwner::canSendMessage(void)
|
||||
{
|
||||
return (mMessagePipe != NULL);
|
||||
}
|
||||
|
||||
bool LLPluginMessagePipeOwner::writeMessageRaw(const std::string &message)
|
||||
{
|
||||
bool result = true;
|
||||
if(mMessagePipe != NULL)
|
||||
{
|
||||
result = mMessagePipe->addMessage(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "dropping message: " << message << LL_ENDL;
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLPluginMessagePipeOwner::killMessagePipe(void)
|
||||
{
|
||||
if(mMessagePipe != NULL)
|
||||
{
|
||||
delete mMessagePipe;
|
||||
mMessagePipe = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket)
|
||||
{
|
||||
mOwner = owner;
|
||||
mOwner->setMessagePipe(this);
|
||||
mSocket = socket;
|
||||
}
|
||||
|
||||
LLPluginMessagePipe::~LLPluginMessagePipe()
|
||||
{
|
||||
if(mOwner != NULL)
|
||||
{
|
||||
mOwner->setMessagePipe(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool LLPluginMessagePipe::addMessage(const std::string &message)
|
||||
{
|
||||
// queue the message for later output
|
||||
mOutput += message;
|
||||
mOutput += MESSAGE_DELIMITER; // message separator
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LLPluginMessagePipe::clearOwner(void)
|
||||
{
|
||||
// The owner is done with this pipe. The next call to process_impl should send any remaining data and exit.
|
||||
mOwner = NULL;
|
||||
}
|
||||
|
||||
void LLPluginMessagePipe::setSocketTimeout(apr_interval_time_t timeout_usec)
|
||||
{
|
||||
// We never want to sleep forever, so force negative timeouts to become non-blocking.
|
||||
|
||||
// according to this page: http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-13.html
|
||||
// blocking/non-blocking with apr sockets is somewhat non-portable.
|
||||
|
||||
if(timeout_usec <= 0)
|
||||
{
|
||||
// Make the socket non-blocking
|
||||
apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1);
|
||||
apr_socket_timeout_set(mSocket->getSocket(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make the socket blocking-with-timeout
|
||||
apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1);
|
||||
apr_socket_timeout_set(mSocket->getSocket(), timeout_usec);
|
||||
}
|
||||
}
|
||||
|
||||
bool LLPluginMessagePipe::pump(F64 timeout)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if(mSocket)
|
||||
{
|
||||
apr_status_t status;
|
||||
apr_size_t size;
|
||||
|
||||
if(!mOutput.empty())
|
||||
{
|
||||
// write any outgoing messages
|
||||
size = (apr_size_t)mOutput.size();
|
||||
|
||||
setSocketTimeout(0);
|
||||
|
||||
// LL_INFOS("Plugin") << "before apr_socket_send, size = " << size << LL_ENDL;
|
||||
|
||||
status = apr_socket_send(
|
||||
mSocket->getSocket(),
|
||||
(const char*)mOutput.data(),
|
||||
&size);
|
||||
|
||||
// LL_INFOS("Plugin") << "after apr_socket_send, size = " << size << LL_ENDL;
|
||||
|
||||
if(status == APR_SUCCESS)
|
||||
{
|
||||
// success
|
||||
mOutput = mOutput.substr(size);
|
||||
}
|
||||
else if(APR_STATUS_IS_EAGAIN(status))
|
||||
{
|
||||
// Socket buffer is full...
|
||||
// remove the written part from the buffer and try again later.
|
||||
mOutput = mOutput.substr(size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// some other error
|
||||
// Treat this as fatal.
|
||||
ll_apr_warn_status(status);
|
||||
|
||||
if(mOwner)
|
||||
{
|
||||
mOwner->socketError(status);
|
||||
}
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
#if LL_WINDOWS
|
||||
if(result)
|
||||
{
|
||||
if(timeout != 0.0f)
|
||||
{
|
||||
ms_sleep((int)(timeout * 1000.0f));
|
||||
timeout = 0.0f;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check for incoming messages
|
||||
if(result)
|
||||
{
|
||||
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;
|
||||
|
||||
// and use the timeout so we'll sleep if no data is available.
|
||||
setSocketTimeout((apr_interval_time_t)(timeout * 1000000));
|
||||
|
||||
while(1)
|
||||
{
|
||||
size = request_size;
|
||||
|
||||
// LL_INFOS("Plugin") << "before apr_socket_recv, size = " << size << LL_ENDL;
|
||||
|
||||
status = apr_socket_recv(
|
||||
mSocket->getSocket(),
|
||||
input_buf,
|
||||
&size);
|
||||
|
||||
// LL_INFOS("Plugin") << "after apr_socket_recv, size = " << size << LL_ENDL;
|
||||
|
||||
if(size > 0)
|
||||
mInput.append(input_buf, size);
|
||||
|
||||
if(status == APR_SUCCESS)
|
||||
{
|
||||
// llinfos << "success, read " << size << llendl;
|
||||
|
||||
if(size != request_size)
|
||||
{
|
||||
// This was a short read, so we're done.
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(APR_STATUS_IS_TIMEUP(status))
|
||||
{
|
||||
// llinfos << "TIMEUP, read " << size << llendl;
|
||||
|
||||
// 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;
|
||||
|
||||
// We've been doing partial reads, and we're done now.
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// some other error
|
||||
// Treat this as fatal.
|
||||
ll_apr_warn_status(status);
|
||||
|
||||
if(mOwner)
|
||||
{
|
||||
mOwner->socketError(status);
|
||||
}
|
||||
result = false;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Let the owner process this message
|
||||
mOwner->receiveMessageRaw(mInput.substr(start, delim - start));
|
||||
|
||||
start = delim + 1;
|
||||
}
|
||||
|
||||
// Remove delivered messages from the input buffer.
|
||||
if(start != 0)
|
||||
mInput = mInput.substr(start);
|
||||
|
||||
}
|
||||
|
||||
92
indra/llplugin/llpluginmessagepipe.h
Normal file
92
indra/llplugin/llpluginmessagepipe.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @file llpluginmessagepipe.h
|
||||
* @brief Classes that implement connections from the plugin system to pipes/pumps.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPLUGINMESSAGEPIPE_H
|
||||
#define LL_LLPLUGINMESSAGEPIPE_H
|
||||
|
||||
#include "lliosocket.h"
|
||||
|
||||
class LLPluginMessagePipe;
|
||||
|
||||
// Inherit from this to be able to receive messages from the LLPluginMessagePipe
|
||||
class LLPluginMessagePipeOwner
|
||||
{
|
||||
LOG_CLASS(LLPluginMessagePipeOwner);
|
||||
public:
|
||||
LLPluginMessagePipeOwner();
|
||||
virtual ~LLPluginMessagePipeOwner();
|
||||
// called with incoming messages
|
||||
virtual void receiveMessageRaw(const std::string &message) = 0;
|
||||
// called when the socket has an error
|
||||
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) ;
|
||||
|
||||
protected:
|
||||
// returns false if writeMessageRaw() would drop the message
|
||||
bool canSendMessage(void);
|
||||
// call this to send a message over the pipe
|
||||
bool writeMessageRaw(const std::string &message);
|
||||
// call this to close the pipe
|
||||
void killMessagePipe(void);
|
||||
|
||||
LLPluginMessagePipe *mMessagePipe;
|
||||
apr_status_t mSocketError;
|
||||
};
|
||||
|
||||
class LLPluginMessagePipe
|
||||
{
|
||||
LOG_CLASS(LLPluginMessagePipe);
|
||||
public:
|
||||
LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket);
|
||||
virtual ~LLPluginMessagePipe();
|
||||
|
||||
bool addMessage(const std::string &message);
|
||||
void clearOwner(void);
|
||||
|
||||
bool pump(F64 timeout = 0.0f);
|
||||
|
||||
protected:
|
||||
void processInput(void);
|
||||
|
||||
// used internally by pump()
|
||||
void setSocketTimeout(apr_interval_time_t timeout_usec);
|
||||
|
||||
std::string mInput;
|
||||
std::string mOutput;
|
||||
|
||||
LLPluginMessagePipeOwner *mOwner;
|
||||
LLSocket::ptr_t mSocket;
|
||||
};
|
||||
|
||||
#endif // LL_LLPLUGINMESSAGE_H
|
||||
490
indra/llplugin/llpluginprocesschild.cpp
Normal file
490
indra/llplugin/llpluginprocesschild.cpp
Normal file
@@ -0,0 +1,490 @@
|
||||
/**
|
||||
* @file llpluginprocesschild.cpp
|
||||
* @brief LLPluginProcessChild handles the child side of the external-process plugin API.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llpluginprocesschild.h"
|
||||
#include "llplugininstance.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llpluginmessageclasses.h"
|
||||
|
||||
static const F32 HEARTBEAT_SECONDS = 1.0f;
|
||||
static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time.
|
||||
|
||||
LLPluginProcessChild::LLPluginProcessChild()
|
||||
{
|
||||
mInstance = NULL;
|
||||
mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
|
||||
mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz
|
||||
mCPUElapsed = 0.0f;
|
||||
}
|
||||
|
||||
LLPluginProcessChild::~LLPluginProcessChild()
|
||||
{
|
||||
if(mInstance != NULL)
|
||||
{
|
||||
sendMessageToPlugin(LLPluginMessage("base", "cleanup"));
|
||||
delete mInstance;
|
||||
mInstance = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void LLPluginProcessChild::killSockets(void)
|
||||
{
|
||||
killMessagePipe();
|
||||
mSocket.reset();
|
||||
}
|
||||
|
||||
void LLPluginProcessChild::init(U32 launcher_port)
|
||||
{
|
||||
mLauncherHost = LLHost("127.0.0.1", launcher_port);
|
||||
setState(STATE_INITIALIZED);
|
||||
}
|
||||
|
||||
void LLPluginProcessChild::idle(void)
|
||||
{
|
||||
bool idle_again;
|
||||
do
|
||||
{
|
||||
if(mSocketError != APR_SUCCESS)
|
||||
{
|
||||
LL_INFOS("Plugin") << "message pipe is in error state, moving to STATE_ERROR"<< LL_ENDL;
|
||||
setState(STATE_ERROR);
|
||||
}
|
||||
|
||||
if((mState > STATE_INITIALIZED) && (mMessagePipe == NULL))
|
||||
{
|
||||
// The pipe has been closed -- we're done.
|
||||
// TODO: This could be slightly more subtle, but I'm not sure it needs to be.
|
||||
LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR"<< LL_ENDL;
|
||||
setState(STATE_ERROR);
|
||||
}
|
||||
|
||||
// If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState().
|
||||
// USE THIS CAREFULLY, since it can starve other code. Specifically make sure there's no way to get into a closed cycle and never return.
|
||||
// When in doubt, don't do it.
|
||||
idle_again = false;
|
||||
|
||||
if(mInstance != NULL)
|
||||
{
|
||||
// Provide some time to the plugin
|
||||
mInstance->idle();
|
||||
}
|
||||
|
||||
switch(mState)
|
||||
{
|
||||
case STATE_UNINITIALIZED:
|
||||
break;
|
||||
|
||||
case STATE_INITIALIZED:
|
||||
if(mSocket->blockingConnect(mLauncherHost))
|
||||
{
|
||||
// This automatically sets mMessagePipe
|
||||
new LLPluginMessagePipe(this, mSocket);
|
||||
|
||||
setState(STATE_CONNECTED);
|
||||
}
|
||||
else
|
||||
{
|
||||
// connect failed
|
||||
setState(STATE_ERROR);
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_CONNECTED:
|
||||
sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello"));
|
||||
setState(STATE_PLUGIN_LOADING);
|
||||
break;
|
||||
|
||||
case STATE_PLUGIN_LOADING:
|
||||
if(!mPluginFile.empty())
|
||||
{
|
||||
mInstance = new LLPluginInstance(this);
|
||||
if(mInstance->load(mPluginFile) == 0)
|
||||
{
|
||||
mHeartbeat.start();
|
||||
mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS);
|
||||
mCPUElapsed = 0.0f;
|
||||
setState(STATE_PLUGIN_LOADED);
|
||||
}
|
||||
else
|
||||
{
|
||||
setState(STATE_ERROR);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_PLUGIN_LOADED:
|
||||
{
|
||||
setState(STATE_PLUGIN_INITIALIZING);
|
||||
LLPluginMessage message("base", "init");
|
||||
message.setValue("user_data_path", mUserDataPath);
|
||||
sendMessageToPlugin(message);
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_PLUGIN_INITIALIZING:
|
||||
// waiting for init_response...
|
||||
break;
|
||||
|
||||
case STATE_RUNNING:
|
||||
if(mInstance != NULL)
|
||||
{
|
||||
// Provide some time to the plugin
|
||||
LLPluginMessage message("base", "idle");
|
||||
message.setValueReal("time", PLUGIN_IDLE_SECONDS);
|
||||
sendMessageToPlugin(message);
|
||||
|
||||
mInstance->idle();
|
||||
|
||||
if(mHeartbeat.hasExpired())
|
||||
{
|
||||
|
||||
// This just proves that we're not stuck down inside the plugin code.
|
||||
LLPluginMessage heartbeat(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat");
|
||||
|
||||
// Calculate the approximage CPU usage fraction (floating point value between 0 and 1) used by the plugin this heartbeat cycle.
|
||||
// Note that this will not take into account any threads or additional processes the plugin spawns, but it's a first approximation.
|
||||
// If we could write OS-specific functions to query the actual CPU usage of this process, that would be a better approximation.
|
||||
heartbeat.setValueReal("cpu_usage", mCPUElapsed / mHeartbeat.getElapsedTimeF64());
|
||||
|
||||
sendMessageToParent(heartbeat);
|
||||
|
||||
mHeartbeat.reset();
|
||||
mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS);
|
||||
mCPUElapsed = 0.0f;
|
||||
}
|
||||
}
|
||||
// receivePluginMessage will transition to STATE_UNLOADING
|
||||
break;
|
||||
|
||||
case STATE_UNLOADING:
|
||||
if(mInstance != NULL)
|
||||
{
|
||||
sendMessageToPlugin(LLPluginMessage("base", "cleanup"));
|
||||
delete mInstance;
|
||||
mInstance = NULL;
|
||||
}
|
||||
setState(STATE_UNLOADED);
|
||||
break;
|
||||
|
||||
case STATE_UNLOADED:
|
||||
killSockets();
|
||||
setState(STATE_DONE);
|
||||
break;
|
||||
|
||||
case STATE_ERROR:
|
||||
// Close the socket to the launcher
|
||||
killSockets();
|
||||
// TODO: Where do we go from here? Just exit()?
|
||||
setState(STATE_DONE);
|
||||
break;
|
||||
|
||||
case STATE_DONE:
|
||||
// just sit here.
|
||||
break;
|
||||
}
|
||||
|
||||
} while (idle_again);
|
||||
}
|
||||
|
||||
void LLPluginProcessChild::sleep(F64 seconds)
|
||||
{
|
||||
if(mMessagePipe)
|
||||
{
|
||||
mMessagePipe->pump(seconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
ms_sleep((int)(seconds * 1000.0f));
|
||||
}
|
||||
}
|
||||
|
||||
void LLPluginProcessChild::pump(void)
|
||||
{
|
||||
if(mMessagePipe)
|
||||
{
|
||||
mMessagePipe->pump(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Should we warn here?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool LLPluginProcessChild::isRunning(void)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(mState == STATE_RUNNING)
|
||||
result = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLPluginProcessChild::isDone(void)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
switch(mState)
|
||||
{
|
||||
case STATE_DONE:
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message)
|
||||
{
|
||||
std::string buffer = message.generate();
|
||||
|
||||
LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL;
|
||||
LLTimer elapsed;
|
||||
|
||||
mInstance->sendMessage(buffer);
|
||||
|
||||
mCPUElapsed += elapsed.getElapsedTimeF64();
|
||||
}
|
||||
|
||||
void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message)
|
||||
{
|
||||
std::string buffer = message.generate();
|
||||
|
||||
LL_DEBUGS("Plugin") << "Sending to parent: " << buffer << LL_ENDL;
|
||||
|
||||
writeMessageRaw(buffer);
|
||||
}
|
||||
|
||||
void LLPluginProcessChild::receiveMessageRaw(const std::string &message)
|
||||
{
|
||||
// Incoming message from the TCP Socket
|
||||
|
||||
LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL;
|
||||
|
||||
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)
|
||||
{
|
||||
passMessage = false;
|
||||
|
||||
std::string message_name = parsed.getName();
|
||||
if(message_name == "load_plugin")
|
||||
{
|
||||
mPluginFile = parsed.getValue("file");
|
||||
mUserDataPath = parsed.getValue("user_data_path");
|
||||
}
|
||||
else if(message_name == "shm_add")
|
||||
{
|
||||
std::string name = parsed.getValue("name");
|
||||
size_t size = (size_t)parsed.getValueS32("size");
|
||||
|
||||
sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
|
||||
if(iter != mSharedMemoryRegions.end())
|
||||
{
|
||||
// Need to remove the old region first
|
||||
LL_WARNS("Plugin") << "Adding a duplicate shared memory segment!" << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a new region
|
||||
LLPluginSharedMemory *region = new LLPluginSharedMemory;
|
||||
if(region->attach(name, size))
|
||||
{
|
||||
mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region));
|
||||
|
||||
std::stringstream addr;
|
||||
addr << region->getMappedAddress();
|
||||
|
||||
// Send the add notification to the plugin
|
||||
LLPluginMessage message("base", "shm_added");
|
||||
message.setValue("name", name);
|
||||
message.setValueS32("size", (S32)size);
|
||||
message.setValuePointer("address", region->getMappedAddress());
|
||||
sendMessageToPlugin(message);
|
||||
|
||||
// and send the response to the parent
|
||||
message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response");
|
||||
message.setValue("name", name);
|
||||
sendMessageToParent(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if(message_name == "shm_remove")
|
||||
{
|
||||
std::string name = parsed.getValue("name");
|
||||
sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
|
||||
if(iter != mSharedMemoryRegions.end())
|
||||
{
|
||||
// forward the remove request to the plugin -- its response will trigger us to detach the segment.
|
||||
LLPluginMessage message("base", "shm_remove");
|
||||
message.setValue("name", name);
|
||||
sendMessageToPlugin(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "shm_remove for unknown memory segment!" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
else if(message_name == "sleep_time")
|
||||
{
|
||||
mSleepTime = parsed.getValueReal("time");
|
||||
}
|
||||
else if(message_name == "crash")
|
||||
{
|
||||
// Crash the plugin
|
||||
LL_ERRS("Plugin") << "Plugin crash requested." << LL_ENDL;
|
||||
}
|
||||
else if(message_name == "hang")
|
||||
{
|
||||
// Hang the plugin
|
||||
LL_WARNS("Plugin") << "Plugin hang requested." << LL_ENDL;
|
||||
while(1)
|
||||
{
|
||||
// wheeeeeeeee......
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "Unknown internal message from parent: " << message_name << LL_ENDL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(passMessage && mInstance != NULL)
|
||||
{
|
||||
LLTimer elapsed;
|
||||
|
||||
mInstance->sendMessage(message);
|
||||
|
||||
mCPUElapsed += elapsed.getElapsedTimeF64();
|
||||
}
|
||||
}
|
||||
|
||||
/* virtual */
|
||||
void LLPluginProcessChild::receivePluginMessage(const std::string &message)
|
||||
{
|
||||
LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL;
|
||||
|
||||
// Incoming message from the plugin instance
|
||||
bool passMessage = true;
|
||||
|
||||
// FIXME: how should we handle queueing here?
|
||||
|
||||
// Intercept certain base messages (responses to ones sent by this class)
|
||||
{
|
||||
// Decode this message
|
||||
LLPluginMessage parsed;
|
||||
parsed.parse(message);
|
||||
std::string message_class = parsed.getClass();
|
||||
if(message_class == "base")
|
||||
{
|
||||
std::string message_name = parsed.getName();
|
||||
if(message_name == "init_response")
|
||||
{
|
||||
// The plugin has finished initializing.
|
||||
setState(STATE_RUNNING);
|
||||
|
||||
// Don't pass this message up to the parent
|
||||
passMessage = false;
|
||||
|
||||
LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response");
|
||||
LLSD versions = parsed.getValueLLSD("versions");
|
||||
new_message.setValueLLSD("versions", versions);
|
||||
|
||||
if(parsed.hasValue("plugin_version"))
|
||||
{
|
||||
std::string plugin_version = parsed.getValue("plugin_version");
|
||||
new_message.setValueLLSD("plugin_version", plugin_version);
|
||||
}
|
||||
|
||||
// Let the parent know it's loaded and initialized.
|
||||
sendMessageToParent(new_message);
|
||||
}
|
||||
else if(message_name == "shm_remove_response")
|
||||
{
|
||||
// Don't pass this message up to the parent
|
||||
passMessage = false;
|
||||
|
||||
std::string name = parsed.getValue("name");
|
||||
sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
|
||||
if(iter != mSharedMemoryRegions.end())
|
||||
{
|
||||
// detach the shared memory region
|
||||
iter->second->detach();
|
||||
|
||||
// and remove it from our map
|
||||
mSharedMemoryRegions.erase(iter);
|
||||
|
||||
// Finally, send the response to the parent.
|
||||
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response");
|
||||
message.setValue("name", name);
|
||||
sendMessageToParent(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "shm_remove_response for unknown memory segment!" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(passMessage)
|
||||
{
|
||||
LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL;
|
||||
writeMessageRaw(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LLPluginProcessChild::setState(EState state)
|
||||
{
|
||||
LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
|
||||
mState = state;
|
||||
};
|
||||
112
indra/llplugin/llpluginprocesschild.h
Normal file
112
indra/llplugin/llpluginprocesschild.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @file llpluginprocesschild.h
|
||||
* @brief LLPluginProcessChild handles the child side of the external-process plugin API.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPLUGINPROCESSCHILD_H
|
||||
#define LL_LLPLUGINPROCESSCHILD_H
|
||||
|
||||
#include "llpluginmessage.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llplugininstance.h"
|
||||
#include "llhost.h"
|
||||
#include "llpluginsharedmemory.h"
|
||||
|
||||
class LLPluginInstance;
|
||||
|
||||
class LLPluginProcessChild: public LLPluginMessagePipeOwner, public LLPluginInstanceMessageListener
|
||||
{
|
||||
LOG_CLASS(LLPluginProcessChild);
|
||||
public:
|
||||
LLPluginProcessChild();
|
||||
~LLPluginProcessChild();
|
||||
|
||||
void init(U32 launcher_port);
|
||||
void idle(void);
|
||||
void sleep(F64 seconds);
|
||||
void pump();
|
||||
|
||||
// returns true if the plugin is in the steady state (processing messages)
|
||||
bool isRunning(void);
|
||||
|
||||
// returns true if the plugin is unloaded or we're in an unrecoverable error state.
|
||||
bool isDone(void);
|
||||
|
||||
void killSockets(void);
|
||||
|
||||
F64 getSleepTime(void) const { return mSleepTime; };
|
||||
|
||||
void sendMessageToPlugin(const LLPluginMessage &message);
|
||||
void sendMessageToParent(const LLPluginMessage &message);
|
||||
|
||||
// Inherited from LLPluginMessagePipeOwner
|
||||
/* virtual */ void receiveMessageRaw(const std::string &message);
|
||||
|
||||
// Inherited from LLPluginInstanceMessageListener
|
||||
/* virtual */ void receivePluginMessage(const std::string &message);
|
||||
|
||||
private:
|
||||
|
||||
enum EState
|
||||
{
|
||||
STATE_UNINITIALIZED,
|
||||
STATE_INITIALIZED, // init() has been called
|
||||
STATE_CONNECTED, // connected back to launcher
|
||||
STATE_PLUGIN_LOADING, // plugin library needs to be loaded
|
||||
STATE_PLUGIN_LOADED, // plugin library has been loaded
|
||||
STATE_PLUGIN_INITIALIZING, // plugin is processing init message
|
||||
STATE_RUNNING, // steady state (processing messages)
|
||||
STATE_UNLOADING, // plugin has sent shutdown_response and needs to be unloaded
|
||||
STATE_UNLOADED, // plugin has been unloaded
|
||||
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);
|
||||
|
||||
LLHost mLauncherHost;
|
||||
LLSocket::ptr_t mSocket;
|
||||
|
||||
std::string mPluginFile;
|
||||
|
||||
std::string mUserDataPath;
|
||||
|
||||
LLPluginInstance *mInstance;
|
||||
|
||||
typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
|
||||
sharedMemoryRegionsType mSharedMemoryRegions;
|
||||
|
||||
LLTimer mHeartbeat;
|
||||
F64 mSleepTime;
|
||||
F64 mCPUElapsed;
|
||||
|
||||
};
|
||||
|
||||
#endif // LL_LLPLUGINPROCESSCHILD_H
|
||||
714
indra/llplugin/llpluginprocessparent.cpp
Normal file
714
indra/llplugin/llpluginprocessparent.cpp
Normal file
@@ -0,0 +1,714 @@
|
||||
/**
|
||||
* @file llpluginprocessparent.cpp
|
||||
* @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llpluginprocessparent.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llpluginmessageclasses.h"
|
||||
|
||||
#include "llapr.h"
|
||||
|
||||
//virtual
|
||||
LLPluginProcessParentOwner::~LLPluginProcessParentOwner()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner)
|
||||
{
|
||||
mOwner = owner;
|
||||
mBoundPort = 0;
|
||||
mState = STATE_UNINITIALIZED;
|
||||
mDisableTimeout = false;
|
||||
mDebug = false;
|
||||
|
||||
mPluginLaunchTimeout = 60.0f;
|
||||
mPluginLockupTimeout = 30.0f;
|
||||
|
||||
// Don't start the timer here -- start it when we actually launch the plugin process.
|
||||
mHeartbeat.stop();
|
||||
}
|
||||
|
||||
LLPluginProcessParent::~LLPluginProcessParent()
|
||||
{
|
||||
LL_DEBUGS("Plugin") << "destructor" << LL_ENDL;
|
||||
|
||||
// Destroy any remaining shared memory regions
|
||||
sharedMemoryRegionsType::iterator iter;
|
||||
while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end())
|
||||
{
|
||||
// destroy the shared memory region
|
||||
iter->second->destroy();
|
||||
|
||||
// and remove it from our map
|
||||
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();
|
||||
killSockets();
|
||||
}
|
||||
|
||||
void LLPluginProcessParent::killSockets(void)
|
||||
{
|
||||
killMessagePipe();
|
||||
mListenSocket.reset();
|
||||
mSocket.reset();
|
||||
}
|
||||
|
||||
void LLPluginProcessParent::errorState(void)
|
||||
{
|
||||
if(mState < STATE_RUNNING)
|
||||
setState(STATE_LAUNCH_FAILURE);
|
||||
else
|
||||
setState(STATE_ERROR);
|
||||
}
|
||||
|
||||
void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path)
|
||||
{
|
||||
mProcess.setExecutable(launcher_filename);
|
||||
mPluginFile = plugin_filename;
|
||||
mCPUUsage = 0.0f;
|
||||
mDebug = debug;
|
||||
mUserDataPath = user_data_path;
|
||||
|
||||
setState(STATE_INITIALIZED);
|
||||
}
|
||||
|
||||
bool LLPluginProcessParent::accept()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
apr_status_t status = APR_EGENERAL;
|
||||
apr_socket_t *new_socket = NULL;
|
||||
|
||||
status = apr_socket_accept(
|
||||
&new_socket,
|
||||
mListenSocket->getSocket(),
|
||||
gAPRPoolp);
|
||||
|
||||
|
||||
if(status == APR_SUCCESS)
|
||||
{
|
||||
// llinfos << "SUCCESS" << llendl;
|
||||
// Success. Create a message pipe on the new socket
|
||||
|
||||
// we MUST create a new pool for the LLSocket, since it will take ownership of it and delete it in its destructor!
|
||||
apr_pool_t* new_pool = NULL;
|
||||
status = apr_pool_create(&new_pool, gAPRPoolp);
|
||||
|
||||
mSocket = LLSocket::create(new_socket, new_pool);
|
||||
new LLPluginMessagePipe(this, mSocket);
|
||||
|
||||
result = true;
|
||||
}
|
||||
else if(APR_STATUS_IS_EAGAIN(status))
|
||||
{
|
||||
// llinfos << "EAGAIN" << llendl;
|
||||
|
||||
// No incoming connections. This is not an error.
|
||||
status = APR_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
// llinfos << "Error:" << llendl;
|
||||
ll_apr_warn_status(status);
|
||||
|
||||
// Some other error.
|
||||
errorState();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLPluginProcessParent::idle(void)
|
||||
{
|
||||
bool idle_again;
|
||||
|
||||
do
|
||||
{
|
||||
// Give time to network processing
|
||||
if(mMessagePipe)
|
||||
{
|
||||
if(!mMessagePipe->pump())
|
||||
{
|
||||
// LL_WARNS("Plugin") << "Message pipe hit an error state" << LL_ENDL;
|
||||
errorState();
|
||||
}
|
||||
}
|
||||
|
||||
if((mSocketError != APR_SUCCESS) && (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 a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState().
|
||||
// USE THIS CAREFULLY, since it can starve other code. Specifically make sure there's no way to get into a closed cycle and never return.
|
||||
// When in doubt, don't do it.
|
||||
idle_again = false;
|
||||
switch(mState)
|
||||
{
|
||||
case STATE_UNINITIALIZED:
|
||||
break;
|
||||
|
||||
case STATE_INITIALIZED:
|
||||
{
|
||||
|
||||
apr_status_t status = APR_SUCCESS;
|
||||
apr_sockaddr_t* addr = NULL;
|
||||
mListenSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
|
||||
mBoundPort = 0;
|
||||
|
||||
// This code is based on parts of LLSocket::create() in lliosocket.cpp.
|
||||
|
||||
status = apr_sockaddr_info_get(
|
||||
&addr,
|
||||
"127.0.0.1",
|
||||
APR_INET,
|
||||
0, // port 0 = ephemeral ("find me a port")
|
||||
0,
|
||||
gAPRPoolp);
|
||||
|
||||
if(ll_apr_warn_status(status))
|
||||
{
|
||||
killSockets();
|
||||
errorState();
|
||||
break;
|
||||
}
|
||||
|
||||
// This allows us to reuse the address on quick down/up. This is unlikely to create problems.
|
||||
ll_apr_warn_status(apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_REUSEADDR, 1));
|
||||
|
||||
status = apr_socket_bind(mListenSocket->getSocket(), addr);
|
||||
if(ll_apr_warn_status(status))
|
||||
{
|
||||
killSockets();
|
||||
errorState();
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the actual port the socket was bound to
|
||||
{
|
||||
apr_sockaddr_t* bound_addr = NULL;
|
||||
if(ll_apr_warn_status(apr_socket_addr_get(&bound_addr, APR_LOCAL, mListenSocket->getSocket())))
|
||||
{
|
||||
killSockets();
|
||||
errorState();
|
||||
break;
|
||||
}
|
||||
mBoundPort = bound_addr->port;
|
||||
|
||||
if(mBoundPort == 0)
|
||||
{
|
||||
LL_WARNS("Plugin") << "Bound port number unknown, bailing out." << LL_ENDL;
|
||||
|
||||
killSockets();
|
||||
errorState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LL_DEBUGS("Plugin") << "Bound tcp socket to port: " << addr->port << LL_ENDL;
|
||||
|
||||
// Make the listen socket non-blocking
|
||||
status = apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_NONBLOCK, 1);
|
||||
if(ll_apr_warn_status(status))
|
||||
{
|
||||
killSockets();
|
||||
errorState();
|
||||
break;
|
||||
}
|
||||
|
||||
apr_socket_timeout_set(mListenSocket->getSocket(), 0);
|
||||
if(ll_apr_warn_status(status))
|
||||
{
|
||||
killSockets();
|
||||
errorState();
|
||||
break;
|
||||
}
|
||||
|
||||
// If it's a stream based socket, we need to tell the OS
|
||||
// to keep a queue of incoming connections for ACCEPT.
|
||||
status = apr_socket_listen(
|
||||
mListenSocket->getSocket(),
|
||||
10); // FIXME: Magic number for queue size
|
||||
|
||||
if(ll_apr_warn_status(status))
|
||||
{
|
||||
killSockets();
|
||||
errorState();
|
||||
break;
|
||||
}
|
||||
|
||||
// If we got here, we're listening.
|
||||
setState(STATE_LISTENING);
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_LISTENING:
|
||||
{
|
||||
// Launch the plugin process.
|
||||
|
||||
// Only argument to the launcher is the port number we're listening on
|
||||
std::stringstream stream;
|
||||
stream << mBoundPort;
|
||||
mProcess.addArgument(stream.str());
|
||||
if(mProcess.launch() != 0)
|
||||
{
|
||||
errorState();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mDebug)
|
||||
{
|
||||
#if LL_DARWIN
|
||||
// If we're set to debug, start up a gdb instance in a new terminal window and have it attach to the plugin process and continue.
|
||||
|
||||
// The command we're constructing would look like this on the command line:
|
||||
// osascript -e 'tell application "Terminal"' -e 'set win to do script "gdb -pid 12345"' -e 'do script "continue" in win' -e 'end tell'
|
||||
|
||||
std::stringstream cmd;
|
||||
|
||||
mDebugger.setExecutable("/usr/bin/osascript");
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("tell application \"Terminal\"");
|
||||
mDebugger.addArgument("-e");
|
||||
cmd << "set win to do script \"gdb -pid " << mProcess.getProcessID() << "\"";
|
||||
mDebugger.addArgument(cmd.str());
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("do script \"continue\" in win");
|
||||
mDebugger.addArgument("-e");
|
||||
mDebugger.addArgument("end tell");
|
||||
mDebugger.launch();
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
// This will allow us to time out if the process never starts.
|
||||
mHeartbeat.start();
|
||||
mHeartbeat.setTimerExpirySec(mPluginLaunchTimeout);
|
||||
setState(STATE_LAUNCHED);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_LAUNCHED:
|
||||
// waiting for the plugin to connect
|
||||
if(pluginLockedUpOrQuit())
|
||||
{
|
||||
errorState();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for the incoming connection.
|
||||
if(accept())
|
||||
{
|
||||
// Stop listening on the server port
|
||||
mListenSocket.reset();
|
||||
setState(STATE_CONNECTED);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_CONNECTED:
|
||||
// waiting for hello message from the plugin
|
||||
|
||||
if(pluginLockedUpOrQuit())
|
||||
{
|
||||
errorState();
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_HELLO:
|
||||
LL_DEBUGS("Plugin") << "received hello message" << llendl;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
setState(STATE_LOADING);
|
||||
break;
|
||||
|
||||
case STATE_LOADING:
|
||||
// The load_plugin_response message will kick us from here into STATE_RUNNING
|
||||
if(pluginLockedUpOrQuit())
|
||||
{
|
||||
errorState();
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_RUNNING:
|
||||
if(pluginLockedUpOrQuit())
|
||||
{
|
||||
errorState();
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_EXITING:
|
||||
if(!mProcess.isRunning())
|
||||
{
|
||||
setState(STATE_CLEANUP);
|
||||
}
|
||||
else if(pluginLockedUp())
|
||||
{
|
||||
LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << llendl;
|
||||
errorState();
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_LAUNCH_FAILURE:
|
||||
if(mOwner != NULL)
|
||||
{
|
||||
mOwner->pluginLaunchFailed();
|
||||
}
|
||||
setState(STATE_CLEANUP);
|
||||
break;
|
||||
|
||||
case STATE_ERROR:
|
||||
if(mOwner != NULL)
|
||||
{
|
||||
mOwner->pluginDied();
|
||||
}
|
||||
setState(STATE_CLEANUP);
|
||||
break;
|
||||
|
||||
case STATE_CLEANUP:
|
||||
// Don't do a kill here anymore -- closing the sockets is the new 'kill'.
|
||||
mProcess.orphan();
|
||||
killSockets();
|
||||
setState(STATE_DONE);
|
||||
break;
|
||||
|
||||
|
||||
case STATE_DONE:
|
||||
// just sit here.
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
} while (idle_again);
|
||||
}
|
||||
|
||||
bool LLPluginProcessParent::isLoading(void)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(mState <= STATE_LOADING)
|
||||
result = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLPluginProcessParent::isRunning(void)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(mState == STATE_RUNNING)
|
||||
result = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLPluginProcessParent::isDone(void)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(mState == STATE_DONE)
|
||||
result = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LLPluginProcessParent::setSleepTime(F64 sleep_time, bool force_send)
|
||||
{
|
||||
if(force_send || (sleep_time != mSleepTime))
|
||||
{
|
||||
// Cache the time locally
|
||||
mSleepTime = sleep_time;
|
||||
|
||||
if(canSendMessage())
|
||||
{
|
||||
// and send to the plugin.
|
||||
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "sleep_time");
|
||||
message.setValueReal("time", mSleepTime);
|
||||
sendMessage(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Too early to send -- the load_plugin_response message will trigger us to send mSleepTime later.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLPluginProcessParent::sendMessage(const LLPluginMessage &message)
|
||||
{
|
||||
|
||||
std::string buffer = message.generate();
|
||||
LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL;
|
||||
writeMessageRaw(buffer);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void LLPluginProcessParent::receiveMessage(const LLPluginMessage &message)
|
||||
{
|
||||
std::string message_class = message.getClass();
|
||||
if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
|
||||
{
|
||||
// internal messages should be handled here
|
||||
std::string message_name = message.getName();
|
||||
if(message_name == "hello")
|
||||
{
|
||||
if(mState == STATE_CONNECTED)
|
||||
{
|
||||
// Plugin host has launched. Tell it which plugin to load.
|
||||
setState(STATE_HELLO);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "received hello message in wrong state -- bailing out" << LL_ENDL;
|
||||
errorState();
|
||||
}
|
||||
|
||||
}
|
||||
else if(message_name == "load_plugin_response")
|
||||
{
|
||||
if(mState == STATE_LOADING)
|
||||
{
|
||||
// Plugin has been loaded.
|
||||
|
||||
mPluginVersionString = message.getValue("plugin_version");
|
||||
LL_INFOS("Plugin") << "plugin version string: " << mPluginVersionString << LL_ENDL;
|
||||
|
||||
// Check which message classes/versions the plugin supports.
|
||||
// TODO: check against current versions
|
||||
// TODO: kill plugin on major mismatches?
|
||||
mMessageClassVersions = message.getValueLLSD("versions");
|
||||
LLSD::map_iterator iter;
|
||||
for(iter = mMessageClassVersions.beginMap(); iter != mMessageClassVersions.endMap(); iter++)
|
||||
{
|
||||
LL_INFOS("Plugin") << "message class: " << iter->first << " -> version: " << iter->second.asString() << LL_ENDL;
|
||||
}
|
||||
|
||||
// Send initial sleep time
|
||||
setSleepTime(mSleepTime, true);
|
||||
|
||||
setState(STATE_RUNNING);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "received load_plugin_response message in wrong state -- bailing out" << LL_ENDL;
|
||||
errorState();
|
||||
}
|
||||
}
|
||||
else if(message_name == "heartbeat")
|
||||
{
|
||||
// this resets our timer.
|
||||
mHeartbeat.setTimerExpirySec(mPluginLockupTimeout);
|
||||
|
||||
mCPUUsage = message.getValueReal("cpu_usage");
|
||||
|
||||
LL_DEBUGS("Plugin") << "cpu usage reported as " << mCPUUsage << LL_ENDL;
|
||||
|
||||
}
|
||||
else if(message_name == "shm_add_response")
|
||||
{
|
||||
// Nothing to do here.
|
||||
}
|
||||
else if(message_name == "shm_remove_response")
|
||||
{
|
||||
std::string name = message.getValue("name");
|
||||
sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
|
||||
|
||||
if(iter != mSharedMemoryRegions.end())
|
||||
{
|
||||
// destroy the shared memory region
|
||||
iter->second->destroy();
|
||||
|
||||
// and remove it from our map
|
||||
mSharedMemoryRegions.erase(iter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "Unknown internal message from child: " << message_name << LL_ENDL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mOwner != NULL)
|
||||
{
|
||||
mOwner->receivePluginMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string LLPluginProcessParent::addSharedMemory(size_t size)
|
||||
{
|
||||
std::string name;
|
||||
|
||||
LLPluginSharedMemory *region = new LLPluginSharedMemory;
|
||||
|
||||
// This is a new region
|
||||
if(region->create(size))
|
||||
{
|
||||
name = region->getName();
|
||||
|
||||
mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region));
|
||||
|
||||
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add");
|
||||
message.setValue("name", name);
|
||||
message.setValueS32("size", (S32)size);
|
||||
sendMessage(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL;
|
||||
|
||||
// Don't leak
|
||||
delete region;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
void LLPluginProcessParent::removeSharedMemory(const std::string &name)
|
||||
{
|
||||
sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
|
||||
|
||||
if(iter != mSharedMemoryRegions.end())
|
||||
{
|
||||
// This segment exists. Send the message to the child to unmap it. The response will cause the parent to unmap our end.
|
||||
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove");
|
||||
message.setValue("name", name);
|
||||
sendMessage(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("Plugin") << "Request to remove an unknown shared memory segment." << LL_ENDL;
|
||||
}
|
||||
}
|
||||
size_t LLPluginProcessParent::getSharedMemorySize(const std::string &name)
|
||||
{
|
||||
size_t result = 0;
|
||||
|
||||
sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
|
||||
if(iter != mSharedMemoryRegions.end())
|
||||
{
|
||||
result = iter->second->getSize();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
void *LLPluginProcessParent::getSharedMemoryAddress(const std::string &name)
|
||||
{
|
||||
void *result = NULL;
|
||||
|
||||
sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
|
||||
if(iter != mSharedMemoryRegions.end())
|
||||
{
|
||||
result = iter->second->getMappedAddress();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string LLPluginProcessParent::getMessageClassVersion(const std::string &message_class)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if(mMessageClassVersions.has(message_class))
|
||||
{
|
||||
result = mMessageClassVersions[message_class].asString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string LLPluginProcessParent::getPluginVersion(void)
|
||||
{
|
||||
return mPluginVersionString;
|
||||
}
|
||||
|
||||
void LLPluginProcessParent::setState(EState state)
|
||||
{
|
||||
LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
|
||||
mState = state;
|
||||
};
|
||||
|
||||
bool LLPluginProcessParent::pluginLockedUpOrQuit()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if(!mDisableTimeout && !mDebug)
|
||||
{
|
||||
if(!mProcess.isRunning())
|
||||
{
|
||||
LL_WARNS("Plugin") << "child exited" << llendl;
|
||||
result = true;
|
||||
}
|
||||
else if(pluginLockedUp())
|
||||
{
|
||||
LL_WARNS("Plugin") << "timeout" << llendl;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LLPluginProcessParent::pluginLockedUp()
|
||||
{
|
||||
// If the timer is running and has expired, the plugin has locked up.
|
||||
return (mHeartbeat.getStarted() && mHeartbeat.hasExpired());
|
||||
}
|
||||
|
||||
169
indra/llplugin/llpluginprocessparent.h
Normal file
169
indra/llplugin/llpluginprocessparent.h
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* @file llpluginprocessparent.h
|
||||
* @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPLUGINPROCESSPARENT_H
|
||||
#define LL_LLPLUGINPROCESSPARENT_H
|
||||
|
||||
#include "llapr.h"
|
||||
#include "llprocesslauncher.h"
|
||||
#include "llpluginmessage.h"
|
||||
#include "llpluginmessagepipe.h"
|
||||
#include "llpluginsharedmemory.h"
|
||||
|
||||
#include "lliosocket.h"
|
||||
|
||||
class LLPluginProcessParentOwner
|
||||
{
|
||||
public:
|
||||
virtual ~LLPluginProcessParentOwner();
|
||||
virtual void receivePluginMessage(const LLPluginMessage &message) = 0;
|
||||
// This will only be called when the plugin has died unexpectedly
|
||||
virtual void pluginLaunchFailed() {};
|
||||
virtual void pluginDied() {};
|
||||
};
|
||||
|
||||
class LLPluginProcessParent : public LLPluginMessagePipeOwner
|
||||
{
|
||||
LOG_CLASS(LLPluginProcessParent);
|
||||
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 idle(void);
|
||||
|
||||
// returns true if the plugin is on its way to steady state
|
||||
bool isLoading(void);
|
||||
|
||||
// returns true if the plugin is in the steady state (processing messages)
|
||||
bool isRunning(void);
|
||||
|
||||
// returns true if the process has exited or we've had a fatal error
|
||||
bool isDone(void);
|
||||
|
||||
void killSockets(void);
|
||||
|
||||
// Go to the proper error state
|
||||
void errorState(void);
|
||||
|
||||
void setSleepTime(F64 sleep_time, bool force_send = false);
|
||||
F64 getSleepTime(void) const { return mSleepTime; };
|
||||
|
||||
void sendMessage(const LLPluginMessage &message);
|
||||
|
||||
void receiveMessage(const LLPluginMessage &message);
|
||||
|
||||
// Inherited from LLPluginMessagePipeOwner
|
||||
void receiveMessageRaw(const std::string &message);
|
||||
|
||||
// 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.
|
||||
std::string addSharedMemory(size_t size);
|
||||
// Negotiates for the removal of a shared memory segment. It is the caller's responsibility to ensure that nothing touches the memory
|
||||
// after this has been called, since the segment will be unmapped shortly thereafter.
|
||||
void removeSharedMemory(const std::string &name);
|
||||
size_t getSharedMemorySize(const std::string &name);
|
||||
void *getSharedMemoryAddress(const std::string &name);
|
||||
|
||||
// Returns the version string the plugin indicated for the message class, or an empty string if that class wasn't in the list.
|
||||
std::string getMessageClassVersion(const std::string &message_class);
|
||||
|
||||
std::string getPluginVersion(void);
|
||||
|
||||
bool getDisableTimeout() { return mDisableTimeout; };
|
||||
void setDisableTimeout(bool disable) { mDisableTimeout = disable; };
|
||||
|
||||
void setLaunchTimeout(F32 timeout) { mPluginLaunchTimeout = timeout; };
|
||||
void setLockupTimeout(F32 timeout) { mPluginLockupTimeout = timeout; };
|
||||
|
||||
F64 getCPUUsage() { return mCPUUsage; };
|
||||
|
||||
private:
|
||||
|
||||
enum EState
|
||||
{
|
||||
STATE_UNINITIALIZED,
|
||||
STATE_INITIALIZED, // init() has been called
|
||||
STATE_LISTENING, // listening for incoming connection
|
||||
STATE_LAUNCHED, // process has been launched
|
||||
STATE_CONNECTED, // process has connected
|
||||
STATE_HELLO, // first message from the plugin process has been received
|
||||
STATE_LOADING, // process has been asked to load the plugin
|
||||
STATE_RUNNING, //
|
||||
STATE_LAUNCH_FAILURE, // Failure before plugin loaded
|
||||
STATE_ERROR, // generic bailout state
|
||||
STATE_CLEANUP, // clean everything up
|
||||
STATE_EXITING, // Tried to kill process, waiting for it to exit
|
||||
STATE_DONE //
|
||||
|
||||
};
|
||||
EState mState;
|
||||
void setState(EState state);
|
||||
|
||||
bool pluginLockedUp();
|
||||
bool pluginLockedUpOrQuit();
|
||||
|
||||
bool accept();
|
||||
|
||||
LLSocket::ptr_t mListenSocket;
|
||||
LLSocket::ptr_t mSocket;
|
||||
U32 mBoundPort;
|
||||
|
||||
LLProcessLauncher mProcess;
|
||||
|
||||
std::string mPluginFile;
|
||||
|
||||
std::string mUserDataPath;
|
||||
|
||||
LLPluginProcessParentOwner *mOwner;
|
||||
|
||||
typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
|
||||
sharedMemoryRegionsType mSharedMemoryRegions;
|
||||
|
||||
LLSD mMessageClassVersions;
|
||||
std::string mPluginVersionString;
|
||||
|
||||
LLTimer mHeartbeat;
|
||||
F64 mSleepTime;
|
||||
F64 mCPUUsage;
|
||||
|
||||
bool mDisableTimeout;
|
||||
bool mDebug;
|
||||
|
||||
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.
|
||||
|
||||
};
|
||||
|
||||
#endif // LL_LLPLUGINPROCESSPARENT_H
|
||||
506
indra/llplugin/llpluginsharedmemory.cpp
Normal file
506
indra/llplugin/llpluginsharedmemory.cpp
Normal file
@@ -0,0 +1,506 @@
|
||||
/**
|
||||
* @file llpluginsharedmemory.cpp
|
||||
* @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llpluginsharedmemory.h"
|
||||
|
||||
// on Mac and Linux, we use the native shm_open/mmap interface by using
|
||||
// #define USE_SHM_OPEN_SHARED_MEMORY 1
|
||||
// in the appropriate sections below.
|
||||
|
||||
// For Windows, use:
|
||||
// #define USE_WIN32_SHARED_MEMORY 1
|
||||
|
||||
// If we ever want to fall back to the apr implementation for a platform, use:
|
||||
// #define USE_APR_SHARED_MEMORY 1
|
||||
|
||||
#if LL_WINDOWS
|
||||
// #define USE_APR_SHARED_MEMORY 1
|
||||
#define USE_WIN32_SHARED_MEMORY 1
|
||||
#elif LL_DARWIN
|
||||
#define USE_SHM_OPEN_SHARED_MEMORY 1
|
||||
#elif LL_LINUX
|
||||
#define USE_SHM_OPEN_SHARED_MEMORY 1
|
||||
#endif
|
||||
|
||||
|
||||
// FIXME: This path thing is evil and unacceptable.
|
||||
#if LL_WINDOWS
|
||||
#define APR_SHARED_MEMORY_PREFIX_STRING "C:\\LLPlugin_"
|
||||
// Apparnently using the "Global\\" prefix here only works from administrative accounts under Vista.
|
||||
// Other options I've seen referenced are "Local\\" and "Session\\".
|
||||
#define WIN32_SHARED_MEMORY_PREFIX_STRING "Local\\LL_"
|
||||
#else
|
||||
// mac and linux
|
||||
#define APR_SHARED_MEMORY_PREFIX_STRING "/tmp/LLPlugin_"
|
||||
#define SHM_OPEN_SHARED_MEMORY_PREFIX_STRING "/LL"
|
||||
#endif
|
||||
|
||||
#if USE_APR_SHARED_MEMORY
|
||||
#include "llapr.h"
|
||||
#include "apr_shm.h"
|
||||
#elif USE_SHM_OPEN_SHARED_MEMORY
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <errno.h>
|
||||
#elif USE_WIN32_SHARED_MEMORY
|
||||
#include <windows.h>
|
||||
#endif // USE_APR_SHARED_MEMORY
|
||||
|
||||
|
||||
int LLPluginSharedMemory::sSegmentNumber = 0;
|
||||
|
||||
std::string LLPluginSharedMemory::createName(void)
|
||||
{
|
||||
std::stringstream newname;
|
||||
|
||||
#if LL_WINDOWS
|
||||
newname << GetCurrentProcessId();
|
||||
#else // LL_WINDOWS
|
||||
newname << getpid();
|
||||
#endif // LL_WINDOWS
|
||||
|
||||
newname << "_" << sSegmentNumber++;
|
||||
|
||||
return newname.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief LLPluginSharedMemoryImpl is the platform-dependent implementation of LLPluginSharedMemory. TODO:DOC is this necessary/sufficient? kinda obvious.
|
||||
*
|
||||
*/
|
||||
class LLPluginSharedMemoryPlatformImpl
|
||||
{
|
||||
public:
|
||||
LLPluginSharedMemoryPlatformImpl();
|
||||
~LLPluginSharedMemoryPlatformImpl();
|
||||
|
||||
#if USE_APR_SHARED_MEMORY
|
||||
apr_shm_t* mAprSharedMemory;
|
||||
#elif USE_SHM_OPEN_SHARED_MEMORY
|
||||
int mSharedMemoryFD;
|
||||
#elif USE_WIN32_SHARED_MEMORY
|
||||
HANDLE mMapFile;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor. Creates a shared memory segment.
|
||||
*/
|
||||
LLPluginSharedMemory::LLPluginSharedMemory()
|
||||
{
|
||||
mSize = 0;
|
||||
mMappedAddress = NULL;
|
||||
mNeedsDestroy = false;
|
||||
|
||||
mImpl = new LLPluginSharedMemoryPlatformImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor. Uses destroy() and detach() to ensure shared memory segment is cleaned up.
|
||||
*/
|
||||
LLPluginSharedMemory::~LLPluginSharedMemory()
|
||||
{
|
||||
if(mNeedsDestroy)
|
||||
destroy();
|
||||
else
|
||||
detach();
|
||||
|
||||
unlink();
|
||||
|
||||
delete mImpl;
|
||||
}
|
||||
|
||||
#if USE_APR_SHARED_MEMORY
|
||||
// MARK: apr implementation
|
||||
|
||||
LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
|
||||
{
|
||||
mAprSharedMemory = NULL;
|
||||
}
|
||||
|
||||
LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::map(void)
|
||||
{
|
||||
mMappedAddress = apr_shm_baseaddr_get(mImpl->mAprSharedMemory);
|
||||
if(mMappedAddress == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::unmap(void)
|
||||
{
|
||||
// This is a no-op under apr.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::close(void)
|
||||
{
|
||||
// This is a no-op under apr.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::unlink(void)
|
||||
{
|
||||
// This is a no-op under apr.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool LLPluginSharedMemory::create(size_t size)
|
||||
{
|
||||
mName = APR_SHARED_MEMORY_PREFIX_STRING;
|
||||
mName += createName();
|
||||
mSize = size;
|
||||
|
||||
apr_status_t status = apr_shm_create( &(mImpl->mAprSharedMemory), mSize, mName.c_str(), gAPRPoolp );
|
||||
|
||||
if(ll_apr_warn_status(status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mNeedsDestroy = true;
|
||||
|
||||
return map();
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::destroy(void)
|
||||
{
|
||||
if(mImpl->mAprSharedMemory)
|
||||
{
|
||||
apr_status_t status = apr_shm_destroy(mImpl->mAprSharedMemory);
|
||||
if(ll_apr_warn_status(status))
|
||||
{
|
||||
// TODO: Is this a fatal error? I think not...
|
||||
}
|
||||
mImpl->mAprSharedMemory = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::attach(const std::string &name, size_t size)
|
||||
{
|
||||
mName = name;
|
||||
mSize = size;
|
||||
|
||||
apr_status_t status = apr_shm_attach( &(mImpl->mAprSharedMemory), mName.c_str(), gAPRPoolp );
|
||||
|
||||
if(ll_apr_warn_status(status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return map();
|
||||
}
|
||||
|
||||
|
||||
bool LLPluginSharedMemory::detach(void)
|
||||
{
|
||||
if(mImpl->mAprSharedMemory)
|
||||
{
|
||||
apr_status_t status = apr_shm_detach(mImpl->mAprSharedMemory);
|
||||
if(ll_apr_warn_status(status))
|
||||
{
|
||||
// TODO: Is this a fatal error? I think not...
|
||||
}
|
||||
mImpl->mAprSharedMemory = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#elif USE_SHM_OPEN_SHARED_MEMORY
|
||||
// MARK: shm_open/mmap implementation
|
||||
|
||||
LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
|
||||
{
|
||||
mSharedMemoryFD = -1;
|
||||
}
|
||||
|
||||
LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
|
||||
{
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::map(void)
|
||||
{
|
||||
mMappedAddress = ::mmap(NULL, mSize, PROT_READ | PROT_WRITE, MAP_SHARED, mImpl->mSharedMemoryFD, 0);
|
||||
if(mMappedAddress == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::unmap(void)
|
||||
{
|
||||
if(mMappedAddress != NULL)
|
||||
{
|
||||
LL_DEBUGS("Plugin") << "calling munmap(" << mMappedAddress << ", " << mSize << ")" << LL_ENDL;
|
||||
if(::munmap(mMappedAddress, mSize) == -1)
|
||||
{
|
||||
// TODO: Is this a fatal error? I think not...
|
||||
}
|
||||
|
||||
mMappedAddress = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::close(void)
|
||||
{
|
||||
if(mImpl->mSharedMemoryFD != -1)
|
||||
{
|
||||
LL_DEBUGS("Plugin") << "calling close(" << mImpl->mSharedMemoryFD << ")" << LL_ENDL;
|
||||
if(::close(mImpl->mSharedMemoryFD) == -1)
|
||||
{
|
||||
// TODO: Is this a fatal error? I think not...
|
||||
}
|
||||
|
||||
mImpl->mSharedMemoryFD = -1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::unlink(void)
|
||||
{
|
||||
if(!mName.empty())
|
||||
{
|
||||
if(::shm_unlink(mName.c_str()) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool LLPluginSharedMemory::create(size_t size)
|
||||
{
|
||||
mName = SHM_OPEN_SHARED_MEMORY_PREFIX_STRING;
|
||||
mName += createName();
|
||||
mSize = size;
|
||||
|
||||
// Preemptive unlink, just in case something didn't get cleaned up.
|
||||
unlink();
|
||||
|
||||
mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if(mImpl->mSharedMemoryFD == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
mNeedsDestroy = true;
|
||||
|
||||
if(::ftruncate(mImpl->mSharedMemoryFD, mSize) == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return map();
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::destroy(void)
|
||||
{
|
||||
unmap();
|
||||
close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool LLPluginSharedMemory::attach(const std::string &name, size_t size)
|
||||
{
|
||||
mName = name;
|
||||
mSize = size;
|
||||
|
||||
mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if(mImpl->mSharedMemoryFD == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// unlink here so the segment will be cleaned up automatically after the last close.
|
||||
unlink();
|
||||
|
||||
return map();
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::detach(void)
|
||||
{
|
||||
unmap();
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
#elif USE_WIN32_SHARED_MEMORY
|
||||
// MARK: Win32 CreateFileMapping-based implementation
|
||||
|
||||
// Reference: http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx
|
||||
|
||||
LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
|
||||
{
|
||||
mMapFile = NULL;
|
||||
}
|
||||
|
||||
LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::map(void)
|
||||
{
|
||||
mMappedAddress = MapViewOfFile(
|
||||
mImpl->mMapFile, // handle to map object
|
||||
FILE_MAP_ALL_ACCESS, // read/write permission
|
||||
0,
|
||||
0,
|
||||
mSize);
|
||||
|
||||
if(mMappedAddress == NULL)
|
||||
{
|
||||
LL_WARNS("Plugin") << "MapViewOfFile failed: " << GetLastError() << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::unmap(void)
|
||||
{
|
||||
if(mMappedAddress != NULL)
|
||||
{
|
||||
UnmapViewOfFile(mMappedAddress);
|
||||
mMappedAddress = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::close(void)
|
||||
{
|
||||
if(mImpl->mMapFile != NULL)
|
||||
{
|
||||
CloseHandle(mImpl->mMapFile);
|
||||
mImpl->mMapFile = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::unlink(void)
|
||||
{
|
||||
// This is a no-op on Windows.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool LLPluginSharedMemory::create(size_t size)
|
||||
{
|
||||
mName = WIN32_SHARED_MEMORY_PREFIX_STRING;
|
||||
mName += createName();
|
||||
mSize = size;
|
||||
|
||||
mImpl->mMapFile = CreateFileMappingA(
|
||||
INVALID_HANDLE_VALUE, // use paging file
|
||||
NULL, // default security
|
||||
PAGE_READWRITE, // read/write access
|
||||
0, // max. object size
|
||||
mSize, // buffer size
|
||||
mName.c_str()); // name of mapping object
|
||||
|
||||
if(mImpl->mMapFile == NULL)
|
||||
{
|
||||
LL_WARNS("Plugin") << "CreateFileMapping failed: " << GetLastError() << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
mNeedsDestroy = true;
|
||||
|
||||
return map();
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::destroy(void)
|
||||
{
|
||||
unmap();
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::attach(const std::string &name, size_t size)
|
||||
{
|
||||
mName = name;
|
||||
mSize = size;
|
||||
|
||||
mImpl->mMapFile = OpenFileMappingA(
|
||||
FILE_MAP_ALL_ACCESS, // read/write access
|
||||
FALSE, // do not inherit the name
|
||||
mName.c_str()); // name of mapping object
|
||||
|
||||
if(mImpl->mMapFile == NULL)
|
||||
{
|
||||
LL_WARNS("Plugin") << "OpenFileMapping failed: " << GetLastError() << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
|
||||
return map();
|
||||
}
|
||||
|
||||
bool LLPluginSharedMemory::detach(void)
|
||||
{
|
||||
unmap();
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
130
indra/llplugin/llpluginsharedmemory.h
Normal file
130
indra/llplugin/llpluginsharedmemory.h
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* @file llpluginsharedmemory.h
|
||||
* @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLPLUGINSHAREDMEMORY_H
|
||||
#define LL_LLPLUGINSHAREDMEMORY_H
|
||||
|
||||
class LLPluginSharedMemoryPlatformImpl;
|
||||
|
||||
/**
|
||||
* @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
|
||||
*
|
||||
*/
|
||||
class LLPluginSharedMemory
|
||||
{
|
||||
LOG_CLASS(LLPluginSharedMemory);
|
||||
public:
|
||||
LLPluginSharedMemory();
|
||||
~LLPluginSharedMemory();
|
||||
|
||||
// Parent will use create/destroy, child will use attach/detach.
|
||||
// Message transactions will ensure child attaches after parent creates and detaches before parent destroys.
|
||||
|
||||
/**
|
||||
* Creates a shared memory segment, with a name which is guaranteed to be unique on the host at the current time. Used by parent.
|
||||
* Message transactions will (? TODO:DOC - should? must?) ensure child attaches after parent creates and detaches before parent destroys.
|
||||
*
|
||||
* @param[in] size Shared memory size in TODO:DOC units = bytes?.
|
||||
*
|
||||
* @return False for failure, true for success.
|
||||
*/
|
||||
bool create(size_t size);
|
||||
/**
|
||||
* Destroys a shared memory segment. Used by parent.
|
||||
* Message transactions will (? TODO:DOC - should? must?) ensure child attaches after parent creates and detaches before parent destroys.
|
||||
*
|
||||
* @return True. TODO:DOC - always returns true. Is this the intended behavior?
|
||||
*/
|
||||
bool destroy(void);
|
||||
|
||||
/**
|
||||
* Creates and attaches a name to a shared memory segment. TODO:DOC what's the difference between attach() and create()?
|
||||
*
|
||||
* @param[in] name Name to attach to memory segment
|
||||
* @param[in] size Size of memory segment TODO:DOC in bytes?
|
||||
*
|
||||
* @return False on failure, true otherwise.
|
||||
*/
|
||||
bool attach(const std::string &name, size_t size);
|
||||
/**
|
||||
* Detaches shared memory segment.
|
||||
*
|
||||
* @return False on failure, true otherwise.
|
||||
*/
|
||||
bool detach(void);
|
||||
|
||||
/**
|
||||
* Checks if shared memory is mapped to a non-null address.
|
||||
*
|
||||
* @return True if memory address is non-null, false otherwise.
|
||||
*/
|
||||
bool isMapped(void) const { return (mMappedAddress != NULL); };
|
||||
/**
|
||||
* Get pointer to shared memory.
|
||||
*
|
||||
* @return Pointer to shared memory.
|
||||
*/
|
||||
void *getMappedAddress(void) const { return mMappedAddress; };
|
||||
/**
|
||||
* Get size of shared memory.
|
||||
*
|
||||
* @return Size of shared memory in bytes. TODO:DOC are bytes the correct unit?
|
||||
*/
|
||||
size_t getSize(void) const { return mSize; };
|
||||
/**
|
||||
* Get name of shared memory.
|
||||
*
|
||||
* @return Name of shared memory.
|
||||
*/
|
||||
std::string getName() const { return mName; };
|
||||
|
||||
private:
|
||||
bool map(void);
|
||||
bool unmap(void);
|
||||
bool close(void);
|
||||
bool unlink(void);
|
||||
|
||||
std::string mName;
|
||||
size_t mSize;
|
||||
void *mMappedAddress;
|
||||
bool mNeedsDestroy;
|
||||
|
||||
LLPluginSharedMemoryPlatformImpl *mImpl;
|
||||
|
||||
static int sSegmentNumber;
|
||||
static std::string createName();
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // LL_LLPLUGINSHAREDMEMORY_H
|
||||
55
indra/llplugin/slplugin/CMakeLists.txt
Normal file
55
indra/llplugin/slplugin/CMakeLists.txt
Normal file
@@ -0,0 +1,55 @@
|
||||
project(SLPlugin)
|
||||
|
||||
include(00-Common)
|
||||
include(LLCommon)
|
||||
include(LLPlugin)
|
||||
include(Linking)
|
||||
include(PluginAPI)
|
||||
include(LLMessage)
|
||||
|
||||
include_directories(
|
||||
${LLPLUGIN_INCLUDE_DIRS}
|
||||
${LLMESSAGE_INCLUDE_DIRS}
|
||||
${LLCOMMON_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
if (DARWIN)
|
||||
include(CMakeFindFrameworks)
|
||||
find_library(CARBON_LIBRARY Carbon)
|
||||
endif (DARWIN)
|
||||
|
||||
|
||||
### SLPlugin
|
||||
|
||||
set(SLPlugin_SOURCE_FILES
|
||||
slplugin.cpp
|
||||
)
|
||||
|
||||
add_executable(SLPlugin
|
||||
WIN32
|
||||
${SLPlugin_SOURCE_FILES}
|
||||
)
|
||||
|
||||
target_link_libraries(SLPlugin
|
||||
${LLPLUGIN_LIBRARIES}
|
||||
${LLMESSAGE_LIBRARIES}
|
||||
${LLCOMMON_LIBRARIES}
|
||||
${PLUGIN_API_WINDOWS_LIBRARIES}
|
||||
)
|
||||
|
||||
add_dependencies(SLPlugin
|
||||
${LLPLUGIN_LIBRARIES}
|
||||
${LLMESSAGE_LIBRARIES}
|
||||
${LLCOMMON_LIBRARIES}
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
endif (DARWIN)
|
||||
|
||||
288
indra/llplugin/slplugin/slplugin.cpp
Normal file
288
indra/llplugin/slplugin/slplugin.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* @file slplugin.cpp
|
||||
* @brief Loader shell for plugins, intended to be launched by the plugin host application, which directly loads a plugin dynamic library.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2008&license=viewergpl$
|
||||
*
|
||||
* Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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
|
||||
*
|
||||
* 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$
|
||||
*/
|
||||
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llpluginprocesschild.h"
|
||||
#include "llpluginmessage.h"
|
||||
#include "llerrorcontrol.h"
|
||||
#include "llapr.h"
|
||||
#include "llstring.h"
|
||||
|
||||
#if LL_DARWIN
|
||||
#include <Carbon/Carbon.h>
|
||||
#endif
|
||||
|
||||
#if LL_DARWIN || LL_LINUX
|
||||
#include <signal.h>
|
||||
#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.
|
||||
|
||||
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
|
||||
|
||||
*/
|
||||
|
||||
#if LL_DARWIN || LL_LINUX
|
||||
// Signal handlers to make crashes not show an OS dialog...
|
||||
static void crash_handler(int sig)
|
||||
{
|
||||
// Just exit cleanly.
|
||||
// TODO: add our own crash reporting
|
||||
_exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LL_WINDOWS
|
||||
#include <windows.h>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Our exception handler - will probably just exit and the host application
|
||||
// will miss the heartbeat and log the error in the usual fashion.
|
||||
LONG WINAPI myWin32ExceptionHandler( struct _EXCEPTION_POINTERS* exception_infop )
|
||||
{
|
||||
//std::cerr << "This plugin (" << __FILE__ << ") - ";
|
||||
//std::cerr << "intercepted an unhandled exception and will exit immediately." << std::endl;
|
||||
|
||||
// TODO: replace exception handler before we exit?
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
// Taken from : http://blog.kalmbachnet.de/?postid=75
|
||||
// The MSVC 2005 CRT forces the call of the default-debugger (normally Dr.Watson)
|
||||
// even with the other exception handling code. This (terrifying) piece of code
|
||||
// patches things so that doesn't happen.
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BOOL PreventSetUnhandledExceptionFilter()
|
||||
{
|
||||
// WARNING: This won't work on 64-bit Windows systems so we turn it off it.
|
||||
// It should work for any flavor of 32-bit Windows we care about.
|
||||
// If it's off, sometimes you will see an OS message when a plugin crashes
|
||||
#ifndef _WIN64
|
||||
HMODULE hKernel32 = LoadLibraryA( "kernel32.dll" );
|
||||
if ( NULL == hKernel32 )
|
||||
return FALSE;
|
||||
|
||||
void *pOrgEntry = GetProcAddress( hKernel32, "SetUnhandledExceptionFilter" );
|
||||
if( NULL == pOrgEntry )
|
||||
return FALSE;
|
||||
|
||||
unsigned char newJump[ 100 ];
|
||||
DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
|
||||
dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far
|
||||
void *pNewFunc = &MyDummySetUnhandledExceptionFilter;
|
||||
DWORD dwNewEntryAddr = (DWORD) pNewFunc;
|
||||
DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
|
||||
|
||||
newJump[ 0 ] = 0xE9; // JMP absolute
|
||||
memcpy( &newJump[ 1 ], &dwRelativeAddr, sizeof( pNewFunc ) );
|
||||
SIZE_T bytesWritten;
|
||||
BOOL bRet = WriteProcessMemory( GetCurrentProcess(), pOrgEntry, newJump, sizeof( pNewFunc ) + 1, &bytesWritten );
|
||||
return bRet;
|
||||
#else
|
||||
return FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Hook our exception handler and replace the system one
|
||||
void initExceptionHandler()
|
||||
{
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER prev_filter;
|
||||
|
||||
// save old exception handler in case we need to restore it at the end
|
||||
prev_filter = SetUnhandledExceptionFilter( myWin32ExceptionHandler );
|
||||
PreventSetUnhandledExceptionFilter();
|
||||
}
|
||||
|
||||
bool checkExceptionHandler()
|
||||
{
|
||||
bool ok = true;
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER prev_filter;
|
||||
prev_filter = SetUnhandledExceptionFilter(myWin32ExceptionHandler);
|
||||
|
||||
PreventSetUnhandledExceptionFilter();
|
||||
|
||||
if (prev_filter != myWin32ExceptionHandler)
|
||||
{
|
||||
LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with " << prev_filter << "!" << LL_ENDL;
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (prev_filter == NULL)
|
||||
{
|
||||
ok = FALSE;
|
||||
if (myWin32ExceptionHandler == NULL)
|
||||
{
|
||||
LL_WARNS("AppInit") << "Exception handler uninitialized." << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with NULL!" << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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
|
||||
int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
|
||||
#else
|
||||
int main(int argc, char **argv)
|
||||
#endif
|
||||
{
|
||||
ll_init_apr();
|
||||
|
||||
// Set up llerror logging
|
||||
{
|
||||
LLError::initForApplication(".");
|
||||
LLError::setDefaultLevel(LLError::LEVEL_INFO);
|
||||
// LLError::setTagLevel("Plugin", LLError::LEVEL_DEBUG);
|
||||
// LLError::logToFile("slplugin.log");
|
||||
}
|
||||
|
||||
#if LL_WINDOWS
|
||||
if( strlen( lpCmdLine ) == 0 )
|
||||
{
|
||||
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
|
||||
// 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
|
||||
if(argc < 2)
|
||||
{
|
||||
LL_ERRS("slplugin") << "usage: " << argv[0] << " launcher_port" << LL_ENDL;
|
||||
}
|
||||
|
||||
U32 port = 0;
|
||||
if(!LLStringUtil::convertToU32(argv[1], port))
|
||||
{
|
||||
LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL;
|
||||
}
|
||||
|
||||
// Catch signals that most kinds of crashes will generate, and exit cleanly so the system crash dialog isn't shown.
|
||||
signal(SIGILL, &crash_handler); // illegal instruction
|
||||
# if LL_DARWIN
|
||||
signal(SIGEMT, &crash_handler); // emulate instruction executed
|
||||
# endif // LL_DARWIN
|
||||
signal(SIGFPE, &crash_handler); // floating-point exception
|
||||
signal(SIGBUS, &crash_handler); // bus error
|
||||
signal(SIGSEGV, &crash_handler); // segmentation violation
|
||||
signal(SIGSYS, &crash_handler); // non-existent system call invoked
|
||||
#endif
|
||||
|
||||
LLPluginProcessChild *plugin = new LLPluginProcessChild();
|
||||
|
||||
plugin->init(port);
|
||||
|
||||
LLTimer timer;
|
||||
timer.start();
|
||||
|
||||
#if LL_WINDOWS
|
||||
checkExceptionHandler();
|
||||
#endif
|
||||
|
||||
while(!plugin->isDone())
|
||||
{
|
||||
timer.reset();
|
||||
plugin->idle();
|
||||
#if LL_DARWIN
|
||||
{
|
||||
// Some plugins (webkit at least) will want an event loop. This qualifies.
|
||||
EventRecord evt;
|
||||
WaitNextEvent(0, &evt, 0, NULL);
|
||||
}
|
||||
#endif
|
||||
F64 elapsed = timer.getElapsedTimeF64();
|
||||
F64 remaining = plugin->getSleepTime() - elapsed;
|
||||
|
||||
if(remaining <= 0.0f)
|
||||
{
|
||||
// We've already used our full allotment.
|
||||
// LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, not sleeping" << LL_ENDL;
|
||||
|
||||
// Still need to service the network...
|
||||
plugin->pump();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, sleeping for " << remaining * 1000.0f << " ms" << LL_ENDL;
|
||||
// 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
|
||||
// exception handler such as QuickTime.
|
||||
//checkExceptionHandler();
|
||||
#endif
|
||||
}
|
||||
|
||||
delete plugin;
|
||||
|
||||
ll_cleanup_apr();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
12
indra/llplugin/slplugin/slplugin_info.plist
Normal file
12
indra/llplugin/slplugin/slplugin_info.plist
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>LSBackgroundOnly</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user