Imported existing code

This commit is contained in:
Hazim Gazov
2010-04-02 02:48:44 -03:00
parent 48fbc5ae91
commit 7a86d01598
13996 changed files with 2468699 additions and 0 deletions

View 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)

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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;
}
}

View 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

View 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);
}
}

View 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

View 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

View 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);
}

View 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

View 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;
};

View 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

View 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());
}

View 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

View 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

View 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

View 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)

View 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;
}

View 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>