Apertura del repositorio

This commit is contained in:
2025-05-24 18:09:39 -03:00
parent 76e6359dad
commit d883ddd0d0
35253 changed files with 2891973 additions and 2 deletions
+48
View File
@@ -0,0 +1,48 @@
# -*- cmake -*-
project(llplugin)
include(00-Common)
include(CURL)
include(LLCommon)
include(LLImage)
include(LLWindow)
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
)
if(NOT ADDRESS_SIZE EQUAL 32)
if(WINDOWS)
##add_definitions(/FIXED:NO)
else(WINDOWS) # not windows therefore gcc LINUX and DARWIN
add_definitions(-fPIC)
endif(WINDOWS)
endif(NOT ADDRESS_SIZE EQUAL 32)
list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES})
add_library (llplugin ${llplugin_SOURCE_FILES})
target_include_directories( llplugin INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries( llplugin llcommon llmath llrender llmessage )
add_subdirectory(slplugin)
File diff suppressed because it is too large Load Diff
+497
View File
@@ -0,0 +1,497 @@
/**
* @file llpluginclassmedia.h
* @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#ifndef LL_LLPLUGINCLASSMEDIA_H
#define LL_LLPLUGINCLASSMEDIA_H
#include "llgltypes.h"
#include "llpluginprocessparent.h"
#include "llrect.h"
#include "llpluginclassmediaowner.h"
#include <queue>
#include "v4color.h"
class LLPluginClassMedia : public LLPluginProcessParentOwner
{
LOG_CLASS(LLPluginClassMedia);
public:
LLPluginClassMedia(LLPluginClassMediaOwner *owner);
virtual ~LLPluginClassMedia();
// local initialization, called by the media manager when creating a source
bool init(const std::string &launcher_filename,
const std::string &plugin_dir,
const std::string &plugin_filename,
bool debug);
// undoes everything init() didm called by the media manager when destroying a source
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; };
F64 getZoomFactor() const { return mZoomFactor; };
// 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);
void setZoomFactor(F64 zoom_factor) { mZoomFactor = zoom_factor; }
void setBackgroundColor(LLColor4 color) { mBackgroundColor = color; };
void setOwner(LLPluginClassMediaOwner *owner) { mOwner = owner; };
// 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, LLSD native_key_data);
void scrollEvent(int x, int y, int clicks_x, int clicks_y, MASK modifiers);
// enable/disable media plugin debugging messages and info spam
void enableMediaPluginDebugging( bool enable );
// Javascript <-> viewer events
void jsEnableObject( bool enable );
void jsAgentLocationEvent( double x, double y, double z );
void jsAgentGlobalLocationEvent( double x, double y, double z );
void jsAgentOrientationEvent( double angle );
void jsAgentLanguageEvent( const std::string& language );
void jsAgentRegionEvent( const std::string& region_name );
void jsAgentMaturityEvent( const std::string& maturity );
// Text may be unicode (utf8 encoded)
bool textInput(const std::string &text, MASK modifiers, LLSD native_key_data);
static std::string sOIDcookieUrl;
static std::string sOIDcookieName;
static std::string sOIDcookieValue;
static std::string sOIDcookieHost;
static std::string sOIDcookiePath;
static bool sOIDcookieHttpOnly;
static bool sOIDcookieSecure;
void storeOpenIDCookie(const std::string url,
const std::string name, const std::string value,
const std::string host, const std::string path,
bool httponly, bool secure);
void injectOpenIDCookie();
void setCookie(std::string uri, std::string name, std::string value, std::string domain, std::string path, bool httponly, bool secure);
void loadURI(const std::string &uri);
void executeJavaScript(const std::string &code);
// "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();
void sendPickFileResponse(const std::vector<std::string> files);
void sendAuthResponse(bool ok, const std::string &username, const std::string &password);
// 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; };
// These can be called before init(), and they will be queued and sent before the media init message.
void setUserDataPath(const std::string &user_data_path_cache, const std::string &username, const std::string &user_data_path_cef_log);
void setLanguageCode(const std::string &language_code);
void setPluginsEnabled(const bool enabled);
void setJavascriptEnabled(const bool enabled);
void setWebSecurityDisabled(const bool disabled);
void setFileAccessFromFileUrlsEnabled(const bool enabled);
void setTarget(const std::string &target);
///////////////////////////////////
// media browser class functions
bool pluginSupportsMediaBrowser(void);
void focus(bool focused);
void set_page_zoom_factor( F64 factor );
void clear_cache();
void clear_cookies();
void set_cookies(const std::string &cookies);
void cookies_enabled(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 setBrowserUserAgent(const std::string& user_agent);
void showWebInspector( bool show );
void proxyWindowOpened(const std::string &target, const std::string &uuid);
void proxyWindowClosed(const std::string &uuid);
void ignore_ssl_cert_errors(bool ignore);
void addCertificateFilePath(const std::string& path);
// 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_NOFOLLOW
std::string getClickNavType() const { return mClickNavType; };
// This is valid after MEDIA_EVENT_CLICK_LINK_HREF
std::string getClickTarget() const { return mClickTarget; };
// This is valid during MEDIA_EVENT_CLICK_LINK_HREF and MEDIA_EVENT_GEOMETRY_CHANGE
std::string getClickUUID() const { return mClickUUID; };
// mClickTarget is received from message and governs how link will be opened
// use this to enforce your own way of opening links inside plugins
void setOverrideClickTarget(const std::string &target);
void resetOverrideClickTarget() { mClickEnforceTarget = false; };
bool isOverrideClickTarget() const { return mClickEnforceTarget; }
std::string getOverrideClickTarget() const { return mOverrideClickTarget; };
// These are valid during MEDIA_EVENT_DEBUG_MESSAGE
std::string getDebugMessageText() const { return mDebugMessageText; };
std::string getDebugMessageLevel() const { return mDebugMessageLevel; };
// This is valid after MEDIA_EVENT_NAVIGATE_ERROR_PAGE
S32 getStatusCode() const { return mStatusCode; };
// These are valid during MEDIA_EVENT_GEOMETRY_CHANGE
S32 getGeometryX() const { return mGeometryX; };
S32 getGeometryY() const { return mGeometryY; };
S32 getGeometryWidth() const { return mGeometryWidth; };
S32 getGeometryHeight() const { return mGeometryHeight; };
// These are valid during MEDIA_EVENT_AUTH_REQUEST
std::string getAuthURL() const { return mAuthURL; };
std::string getAuthRealm() const { return mAuthRealm; };
// These are valid during MEDIA_EVENT_PICK_FILE_REQUEST
bool getIsMultipleFilePick() const { return mIsMultipleFilePick; }
// These are valid during MEDIA_EVENT_LINK_HOVERED
std::string getHoverText() const { return mHoverText; };
std::string getHoverLink() const { return mHoverLink; };
// these are valid during MEDIA_EVENT_LINK_HOVERED
std::string getFileDownloadFilename() const { return mFileDownloadFilename; }
const 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);
std::shared_ptr<LLPluginClassMedia> getSharedPtr() { return std::dynamic_pointer_cast<LLPluginClassMedia>(shared_from_this()); } // due to enable_shared_from_this
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;
F64 mZoomFactor;
float mRequestedVolume;
// Priority of this media stream
EPriority mPriority;
int mLowPrioritySizeLimit;
bool mAllowDownsample;
int mPadding;
LLPluginProcessParent::ptr_t 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;
LLColor4 mBackgroundColor;
std::string mTarget;
/////////////////////////////////////////
// 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 mClickNavType;
std::string mClickTarget;
std::string mClickUUID;
bool mClickEnforceTarget;
std::string mOverrideClickTarget;
std::string mDebugMessageText;
std::string mDebugMessageLevel;
S32 mGeometryX;
S32 mGeometryY;
S32 mGeometryWidth;
S32 mGeometryHeight;
S32 mStatusCode;
std::string mAuthURL;
std::string mAuthRealm;
std::string mHoverText;
std::string mHoverLink;
std::string mFileDownloadFilename;
bool mIsMultipleFilePick;
/////////////////////////////////////////
// media_time class
F64 mCurrentTime;
F64 mDuration;
F64 mCurrentRate;
F64 mLoadedDuration;
public:
// <FS:ND> Enable gstreamer plugin to report title/artist of current stream
std::string const& getArtist() const
{ return mArtist; }
std::string const& getTitle() const
{ return mTitle; }
private:
std::string mArtist;
std::string mTitle;
// </FS:ND>
//<FS:ND> Special settings for CEF
public:
void setFlipY( bool );
// </FS:ND>
//--------------------------------------
//debug use only
//
private:
bool mDeleteOK ;
public:
void setDeleteOK(bool flag) { mDeleteOK = flag ;}
//--------------------------------------
};
#endif // LL_LLPLUGINCLASSMEDIA_H
+90
View File
@@ -0,0 +1,90 @@
/**
* @file llpluginclassmediaowner.h
* @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#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_NAVIGATE_ERROR_PAGE, // browser navigated to a page that resulted in an HTTP error
MEDIA_EVENT_CLICK_LINK_HREF, // I'm not entirely sure what the semantics of these two are
MEDIA_EVENT_CLICK_LINK_NOFOLLOW,
MEDIA_EVENT_CLOSE_REQUEST, // The plugin requested its window be closed (currently hooked up to javascript window.close in webkit)
MEDIA_EVENT_PICK_FILE_REQUEST, // The plugin wants the user to pick a file
MEDIA_EVENT_GEOMETRY_CHANGE, // The plugin requested its window geometry be changed (per the javascript window interface)
MEDIA_EVENT_PLUGIN_FAILED_LAUNCH, // The plugin failed to launch
MEDIA_EVENT_PLUGIN_FAILED, // The plugin died unexpectedly
MEDIA_EVENT_AUTH_REQUEST, // The plugin wants to display an auth dialog
MEDIA_EVENT_FILE_DOWNLOAD, // the plugin wants to download a file
MEDIA_EVENT_DEBUG_MESSAGE, // plugin sending back debug information for host to process
MEDIA_EVENT_LINK_HOVERED // Got a "link hovered" event from the plugin
} 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
+186
View File
@@ -0,0 +1,186 @@
/**
* @file llplugininstance.cpp
* @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#include "linden_common.h"
#include "llplugininstance.h"
#include "llapr.h"
#if LL_WINDOWS
#include "direct.h" // needed for _chdir()
#endif
/** 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(const std::string& plugin_dir, std::string &plugin_file)
{
pluginInitFunction init_function = NULL;
if ( plugin_dir.length() )
{
#if LL_WINDOWS
// VWR-21275:
// *SOME* Windows systems fail to load the Qt plugins if the current working
// directory is not the same as the directory with the Qt DLLs in.
// This should not cause any run time issues since we are changing the cwd for the
// plugin shell process and not the viewer.
// Changing back to the previous directory is not necessary since the plugin shell
// quits once the plugin exits.
_chdir( plugin_dir.c_str() );
#endif
};
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;
}
}
+99
View File
@@ -0,0 +1,99 @@
/**
* @file llplugininstance.h
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#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(const std::string& plugin_dir, 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
+438
View File
@@ -0,0 +1,438 @@
/**
* @file llpluginmessage.cpp
* @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#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);
}
}
+137
View File
@@ -0,0 +1,137 @@
/**
* @file llpluginmessage.h
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#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
+53
View File
@@ -0,0 +1,53 @@
/**
* @file llpluginmessageclasses.h
* @brief This file defines the versions of existing message classes for LLPluginMessage.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#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
+397
View File
@@ -0,0 +1,397 @@
/**
* @file llpluginmessagepipe.cpp
* @brief Classes that implement connections from the plugin system to pipes/pumps.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#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):
mInputMutex(),
mOutputMutex(),
mOutputStartIndex(0),
mOwner(owner),
mSocket(socket)
{
mOwner->setMessagePipe(this);
}
LLPluginMessagePipe::~LLPluginMessagePipe()
{
if(mOwner != NULL)
{
mOwner->setMessagePipe(NULL);
}
}
bool LLPluginMessagePipe::addMessage(const std::string &message)
{
// queue the message for later output
LLMutexLock lock(&mOutputMutex);
// If we're starting to use up too much memory, clear
if (mOutputStartIndex > 1024 * 1024)
{
mOutput = mOutput.substr(mOutputStartIndex);
mOutputStartIndex = 0;
}
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 = pumpOutput();
if(result)
{
result = pumpInput(timeout);
}
return result;
}
bool LLPluginMessagePipe::pumpOutput()
{
bool result = true;
if(mSocket)
{
apr_status_t status;
apr_size_t in_size, out_size;
LLMutexLock lock(&mOutputMutex);
const char * output_data = &(mOutput.data()[mOutputStartIndex]);
if(*output_data != '\0')
{
// write any outgoing messages
in_size = (apr_size_t) (mOutput.size() - mOutputStartIndex);
out_size = in_size;
setSocketTimeout(0);
// LL_INFOS("Plugin") << "before apr_socket_send, size = " << size << LL_ENDL;
status = apr_socket_send(mSocket->getSocket(),
output_data,
&out_size);
// LL_INFOS("Plugin") << "after apr_socket_send, size = " << size << LL_ENDL;
if((status == APR_SUCCESS) || APR_STATUS_IS_EAGAIN(status))
{
// Success or Socket buffer is full...
// If we've pumped the entire string, clear it
if (out_size == in_size)
{
mOutputStartIndex = 0;
mOutput.clear();
}
else
{
llassert(in_size > out_size);
// Remove the written part from the buffer and try again later.
mOutputStartIndex += out_size;
}
}
else if(APR_STATUS_IS_EOF(status))
{
// This is what we normally expect when a plugin exits.
//LL_INFOS() << "Got EOF from plugin socket. " << LL_ENDL;
if(mOwner)
{
mOwner->socketError(status);
}
result = false;
}
else
{
// some other error
// Treat this as fatal.
ll_apr_warn_status(status);
if(mOwner)
{
mOwner->socketError(status);
}
result = false;
}
}
}
return result;
}
bool LLPluginMessagePipe::pumpInput(F64 timeout)
{
bool result = true;
if(mSocket)
{
apr_status_t status;
apr_size_t size;
// FIXME: For some reason, the apr timeout stuff isn't working properly on windows.
// Until such time as we figure out why, don't try to use the socket timeout -- just sleep here instead.
#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;
if(timeout == 0.0f)
{
// If we have no timeout, start out with a full read.
request_size = sizeof(input_buf);
}
else
{
// Start out by reading one byte, so that any data received will wake us up.
request_size = 1;
}
// and use the timeout so we'll sleep if no data is available.
setSocketTimeout((apr_interval_time_t)(timeout * 1000000));
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)
{
LLMutexLock lock(&mInputMutex);
mInput.append(input_buf, size);
}
if(status == APR_SUCCESS)
{
LL_DEBUGS("PluginSocket") << "success, read " << size << LL_ENDL;
if(size != request_size)
{
// This was a short read, so we're done.
break;
}
}
else if(APR_STATUS_IS_TIMEUP(status))
{
LL_DEBUGS("PluginSocket") << "TIMEUP, read " << size << LL_ENDL;
// Timeout was hit. Since the initial read is 1 byte, this should never be a partial read.
break;
}
else if(APR_STATUS_IS_EAGAIN(status))
{
LL_DEBUGS("PluginSocket") << "EAGAIN, read " << size << LL_ENDL;
// Non-blocking read returned immediately.
break;
}
else if(APR_STATUS_IS_EOF(status))
{
// This is what we normally expect when a plugin exits.
//LL_INFOS("PluginSocket") << "Got EOF from plugin socket. " << LL_ENDL;
if(mOwner)
{
mOwner->socketError(status);
}
result = false;
break;
}
else
{
// some other error
// Treat this as fatal.
ll_apr_warn_status(status);
if(mOwner)
{
mOwner->socketError(status);
}
result = false;
break;
}
if(timeout != 0.0f)
{
// Second and subsequent reads should not use the timeout
setSocketTimeout(0);
// and should try to fill the input buffer
request_size = sizeof(input_buf);
}
}
processInput();
}
}
return result;
}
void LLPluginMessagePipe::processInput(void)
{
// Look for input delimiter(s) in the input buffer.
size_t delim;
mInputMutex.lock();
while((delim = mInput.find(MESSAGE_DELIMITER)) != std::string::npos)
{
// Let the owner process this message
if (mOwner)
{
// Pull the message out of the input buffer before calling receiveMessageRaw.
// It's now possible for this function to get called recursively (in the case where the plugin makes a blocking request)
// and this guarantees that the messages will get dequeued correctly.
std::string message(mInput, 0, delim);
mInput.erase(0, delim + 1);
mInputMutex.unlock();
mOwner->receiveMessageRaw(message);
mInputMutex.lock();
}
else
{
LL_WARNS("Plugin") << "!mOwner" << LL_ENDL;
}
}
mInputMutex.unlock();
}
+96
View File
@@ -0,0 +1,96 @@
/**
* @file llpluginmessagepipe.h
* @brief Classes that implement connections from the plugin system to pipes/pumps.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#ifndef LL_LLPLUGINMESSAGEPIPE_H
#define LL_LLPLUGINMESSAGEPIPE_H
#include "lliosocket.h"
#include "llthread.h"
#include "llmutex.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);
bool pumpOutput();
bool pumpInput(F64 timeout = 0.0f);
protected:
void processInput(void);
// used internally by pump()
void setSocketTimeout(apr_interval_time_t timeout_usec);
LLMutex mInputMutex;
std::string mInput;
LLMutex mOutputMutex;
std::string mOutput;
std::string::size_type mOutputStartIndex;
LLPluginMessagePipeOwner *mOwner;
LLSocket::ptr_t mSocket;
};
#endif // LL_LLPLUGINMESSAGE_H
+606
View File
@@ -0,0 +1,606 @@
/**
* @file llpluginprocesschild.cpp
* @brief LLPluginProcessChild handles the child side of the external-process plugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#include "linden_common.h"
#include "llpluginprocesschild.h"
#include "llplugininstance.h"
#include "llpluginmessagepipe.h"
#include "llpluginmessageclasses.h"
static const F32 GOODBYE_SECONDS = 12.0f; // Do not set it to be bigger than mPluginLockupTimeout or parent will kill LLPluginProcessChild
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()
{
mState = STATE_UNINITIALIZED;
mInstance = NULL;
mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz
mCPUElapsed = 0.0f;
mBlockingRequest = false;
mBlockingResponseReceived = false;
}
LLPluginProcessChild::~LLPluginProcessChild()
{
if (mInstance != NULL)
{
sendMessageToPlugin(LLPluginMessage("base", "cleanup"));
// IMPORTANT: under some (unknown) circumstances the apr_dso_unload() triggered when mInstance is deleted
// appears to fail and lock up which means that a given instance of the slplugin process never exits.
// This is bad, especially when users try to update their version of SL - it fails because the slplugin
// process as well as a bunch of plugin specific files are locked and cannot be overwritten.
exit(0);
//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 (mState < STATE_SHUTDOWNREQ)
{ // Once we have hit the shutdown request state checking for errors might put us in a spurious
// error state... don't do that.
if (APR_STATUS_IS_EOF(mSocketError))
{
// Plugin socket was closed. This covers both normal plugin termination and host crashes.
setState(STATE_ERROR);
}
else if (mSocketError != APR_SUCCESS)
{
LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR" << LL_ENDL;
setState(STATE_ERROR);
}
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(mPluginDir, 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");
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_SHUTDOWNREQ:
// set next state first thing in case "cleanup" message advances state.
setState(STATE_UNLOADING);
mWaitGoodbye.setTimerExpirySec(GOODBYE_SECONDS);
if (mInstance != NULL)
{
sendMessageToPlugin(LLPluginMessage("base", "cleanup"));
}
break;
case STATE_UNLOADING:
// waiting for goodbye from plugin.
if (mWaitGoodbye.hasExpired())
{
LL_WARNS() << "Wait for goodbye expired. Advancing to UNLOADED" << LL_ENDL;
if (mInstance != NULL)
{
// Something went wrong, at least make sure plugin will terminate
sendMessageToPlugin(LLPluginMessage("base", "force_exit"));
}
setState(STATE_UNLOADED);
}
if (mInstance)
{
// Provide some time to the plugin
// example: CEF on "cleanup" sets shutdown request, but it still needs idle loop to actually shutdown
LLPluginMessage message("base", "idle");
message.setValueReal("time", PLUGIN_IDLE_SECONDS);
sendMessageToPlugin(message);
mInstance->idle();
}
break;
case STATE_UNLOADED:
killSockets();
delete mInstance;
mInstance = NULL;
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)
{
deliverQueuedMessages();
if (mMessagePipe)
{
mMessagePipe->pump(seconds);
}
else
{
ms_sleep((int)(seconds * 1000.0f));
}
}
void LLPluginProcessChild::pump(void)
{
deliverQueuedMessages();
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)
{
if (mInstance)
{
std::string buffer = message.generate();
LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL;
LLTimer elapsed;
mInstance->sendMessage(buffer);
mCPUElapsed += elapsed.getElapsedTimeF64();
}
else
{
LL_WARNS("Plugin") << "mInstance == NULL" << LL_ENDL;
}
}
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;
// Decode this message
LLPluginMessage parsed;
parsed.parse(message);
if (mBlockingRequest)
{
// We're blocking the plugin waiting for a response.
if (parsed.hasValue("blocking_response"))
{
// This is the message we've been waiting for -- fall through and send it immediately.
mBlockingResponseReceived = true;
}
else
{
// Still waiting. Queue this message and don't process it yet.
mMessageQueue.push(message);
return;
}
}
bool passMessage = true;
// FIXME: how should we handle queueing here?
{
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");
mPluginDir = parsed.getValue("dir");
}
else if (message_name == "shutdown_plugin")
{
setState(STATE_SHUTDOWNREQ);
}
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;
delete region;
}
}
}
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 = llmax(parsed.getValueReal("time"), 1.0 / 100.0); // clamp to maximum of 100Hz
}
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;
if (mBlockingRequest)
{
//
LL_ERRS("Plugin") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL;
}
// Incoming message from the plugin instance
bool passMessage = true;
// 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);
if (parsed.hasValue("blocking_request"))
{
mBlockingRequest = true;
}
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 == "goodbye")
{
setState(STATE_UNLOADED);
}
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);
}
while (mBlockingRequest)
{
// The plugin wants to block and wait for a response to this message.
sleep(mSleepTime); // this will pump the message pipe and process messages
if (mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL))
{
// Response has been received, or we've hit an error state. Stop waiting.
mBlockingRequest = false;
mBlockingResponseReceived = false;
}
}
}
void LLPluginProcessChild::setState(EState state)
{
LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
mState = state;
};
void LLPluginProcessChild::deliverQueuedMessages()
{
if (!mBlockingRequest)
{
while (!mMessageQueue.empty())
{
receiveMessageRaw(mMessageQueue.front());
mMessageQueue.pop();
}
}
}
+115
View File
@@ -0,0 +1,115 @@
/**
* @file llpluginprocesschild.h
* @brief LLPluginProcessChild handles the child side of the external-process plugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#ifndef LL_LLPLUGINPROCESSCHILD_H
#define LL_LLPLUGINPROCESSCHILD_H
#include <queue>
#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_SHUTDOWNREQ, // Parent has requested a shutdown.
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.
};
void setState(EState state);
EState mState;
LLHost mLauncherHost;
LLSocket::ptr_t mSocket;
std::string mPluginFile;
std::string mPluginDir;
LLPluginInstance *mInstance;
typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
sharedMemoryRegionsType mSharedMemoryRegions;
LLTimer mHeartbeat;
F64 mSleepTime;
F64 mCPUElapsed;
bool mBlockingRequest;
bool mBlockingResponseReceived;
std::queue<std::string> mMessageQueue;
LLTimer mWaitGoodbye;
void deliverQueuedMessages();
};
#endif // LL_LLPLUGINPROCESSCHILD_H
File diff suppressed because it is too large Load Diff
+222
View File
@@ -0,0 +1,222 @@
/**
* @file llpluginprocessparent.h
* @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#ifndef LL_LLPLUGINPROCESSPARENT_H
#define LL_LLPLUGINPROCESSPARENT_H
#include <queue>
#include <boost/enable_shared_from_this.hpp>
#include "llapr.h"
#include "llprocess.h"
#include "llpluginmessage.h"
#include "llpluginmessagepipe.h"
#include "llpluginsharedmemory.h"
#include "lliosocket.h"
#include "llthread.h"
#include "llsd.h"
#include "llevents.h"
class LLPluginProcessParentOwner : public std::enable_shared_from_this < LLPluginProcessParentOwner >
{
public:
virtual ~LLPluginProcessParentOwner();
virtual void receivePluginMessage(const LLPluginMessage &message) = 0;
virtual bool receivePluginMessageEarly(const LLPluginMessage &message) {return false;};
// This will only be called when the plugin has died unexpectedly
virtual void pluginLaunchFailed() {};
virtual void pluginDied() {};
};
class LLPluginProcessParent : public LLPluginMessagePipeOwner
{
LOG_CLASS(LLPluginProcessParent);
LLPluginProcessParent(LLPluginProcessParentOwner *owner);
public:
typedef std::shared_ptr<LLPluginProcessParent> ptr_t;
~LLPluginProcessParent();
void init(const std::string &launcher_filename,
const std::string &plugin_dir,
const std::string &plugin_filename,
bool debug);
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);
// returns true if the process is currently waiting on a blocking request
bool isBlocked(void) { return mBlocked; };
void killSockets(void);
// Go to the proper error state
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);
static ptr_t create(LLPluginProcessParentOwner *owner);
void requestShutdown();
// Inherited from LLPluginMessagePipeOwner
/*virtual*/ void receiveMessageRaw(const std::string &message);
/*virtual*/ void receiveMessageEarly(const LLPluginMessage &message);
/*virtual*/ void setMessagePipe(LLPluginMessagePipe *message_pipe) ;
// This adds a memory segment shared with the client, generating a name for the segment. The name generated is guaranteed to be unique on the host.
// The caller must call removeSharedMemory first (and wait until getSharedMemorySize returns 0 for the indicated name) before re-adding a segment with the same name.
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; };
static void poll(F64 timeout);
static bool canPollThreadRun() { return (sPollSet || sPollsetNeedsRebuild || sUseReadThread); };
static void setUseReadThread(bool use_read_thread);
static bool getUseReadThread() { return sUseReadThread; };
static void shutdown();
private:
typedef std::map<void *, ptr_t> mapInstances_t;
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_GOODBYE,
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 wantsPolling() const;
void removeFromProcessing();
bool pluginLockedUp();
bool pluginLockedUpOrQuit();
bool accept();
LLSocket::ptr_t mListenSocket;
LLSocket::ptr_t mSocket;
U32 mBoundPort;
LLProcess::Params mProcessParams;
LLProcessPtr mProcess;
bool mProcessCreationRequested = false;
std::string mPluginFile;
std::string mPluginDir;
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;
bool mBlocked;
bool mPolledInput;
LLProcessPtr mDebugger;
F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch.
F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up.
static bool sUseReadThread;
apr_pollfd_t mPollFD;
static apr_pollset_t *sPollSet;
static bool sPollsetNeedsRebuild;
static LLCoros::Mutex *sInstancesMutex;
static mapInstances_t sInstances;
static void dirtyPollSet();
static void updatePollset();
void servicePoll();
static LLThread *sReadThread;
LLTempBoundListener mPolling;
bool pollTick();
LLMutex mIncomingQueueMutex;
std::queue<LLPluginMessage> mIncomingQueue;
// <FS:ND> FIRE-3877; Bind to a fixed port. Some network drivers (Bigfoot) refuse to tell us to which port a socket is bound if 0 (= choose one) was used.
U32 mPortToBind;
U32 mBindRetries;
// </FS:ND>
};
#endif // LL_LLPLUGINPROCESSPARENT_H
+502
View File
@@ -0,0 +1,502 @@
/**
* @file llpluginsharedmemory.cpp
* LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#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
static_cast<DWORD>(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
+125
View File
@@ -0,0 +1,125 @@
/**
* @file llpluginsharedmemory.h
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
* @endcond
*/
#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
+73
View File
@@ -0,0 +1,73 @@
project(SLPlugin)
include(00-Common)
include(LLCommon)
include(Linking)
include(PluginAPI)
### SLPlugin
set(SLPlugin_SOURCE_FILES
slplugin.cpp
)
if (DARWIN)
list(APPEND SLPlugin_SOURCE_FILES
slplugin-objc.mm
)
list(APPEND SLPlugin_HEADER_FILES
slplugin-objc.h
)
endif (DARWIN)
if (SLPlugin_HEADER_FILES)
list(APPEND SLPlugin_SOURCE_FILES ${SLPlugin_HEADER_FILES})
endif (SLPlugin_HEADER_FILES)
add_executable(SLPlugin
WIN32
MACOSX_BUNDLE
${SLPlugin_SOURCE_FILES}
)
if (WINDOWS)
set_target_properties(SLPlugin
PROPERTIES
LINK_FLAGS_DEBUG "/NODEFAULTLIB:\"LIBCMTD\""
)
else ()
set_target_properties(SLPlugin
PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist
)
endif ()
if(WINDOWS)
set_target_properties(SLPlugin
PROPERTIES
LINK_FLAGS "/debug"
)
endif(WINDOWS)
target_link_libraries(SLPlugin
llplugin
llmessage
llcommon
ll::pluginlibraries
)
if (DARWIN)
# Make sure the app bundle has a Resources directory (it will get populated by viewer-manifest.py later)
add_custom_command(
TARGET SLPlugin POST_BUILD
COMMAND mkdir
ARGS
-p
${CMAKE_CURRENT_BINARY_DIR}/$<IF:$<BOOL:${LL_GENERATOR_IS_MULTI_CONFIG}>,$<CONFIG>,>/SLPlugin.app/Contents/Resources
)
endif (DARWIN)
if (LL_TESTS)
ll_deploy_sharedlibs_command(SLPlugin)
endif (LL_TESTS)
+55
View File
@@ -0,0 +1,55 @@
/**
* @file slplugin-objc.h
* @brief Header file for slplugin-objc.mm.
*
* @cond
*
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*
* @endcond
*/
//Protos for ObjectiveC classes (cannot import cocoa here due to BOOL conflict)
#ifndef __OBJC__
class NSWindow;
#endif // __OBJC__
/* Defined in slplugin-objc.mm: */
class LLCocoaPlugin
{
public:
LLCocoaPlugin();
void setupCocoa();
void createAutoReleasePool();
void deleteAutoReleasePool();
void setupGroup();
void updateWindows();
void processEvents();
public:
//EventTargetRef mEventTarget;
NSWindow* mFrontWindow;
NSWindow* mPluginWindow;
int mHackState;
};
+177
View File
@@ -0,0 +1,177 @@
/**
* @file slplugin-objc.mm
* @brief Objective-C++ file for use with the loader shell, so we can use a couple of Cocoa APIs.
*
* @cond
*
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*
* @endcond
*/
#include <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#include "slplugin-objc.h"
//Note: NSApp is a global defined by cocoa which is an id to the application.
void LLCocoaPlugin::setupCocoa()
{
static bool inited = false;
if(!inited)
{
createAutoReleasePool();
// The following prevents the Cocoa command line parser from trying to open 'unknown' arguements as documents.
// ie. running './secondlife -set Language fr' would cause a pop-up saying can't open document 'fr'
// when init'ing the Cocoa App window.
[[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"];
// This is a bit of voodoo taken from the Apple sample code "CarbonCocoa_PictureCursor":
// http://developer.apple.com/samplecode/CarbonCocoa_PictureCursor/index.html
// Needed for Carbon based applications which call into Cocoa
NSApplicationLoad();
// Must first call [[[NSWindow alloc] init] release] to get the NSWindow machinery set up so that NSCursor can use a window to cache the cursor image
[[[NSWindow alloc] init] release];
mPluginWindow = [NSApp mainWindow];
deleteAutoReleasePool();
inited = true;
}
}
static NSAutoreleasePool *sPool = NULL;
void LLCocoaPlugin::createAutoReleasePool()
{
if(!sPool)
{
sPool = [[NSAutoreleasePool alloc] init];
}
}
void LLCocoaPlugin::deleteAutoReleasePool()
{
if(sPool)
{
[sPool release];
sPool = NULL;
}
}
LLCocoaPlugin::LLCocoaPlugin():mHackState(0)
{
NSArray* window_list = [NSApp orderedWindows];
mFrontWindow = [window_list objectAtIndex:0];
}
void LLCocoaPlugin::processEvents()
{
// Some plugins (webkit at least) will want an event loop. This qualifies.
NSEvent * event;
event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES];
[NSApp sendEvent: event];
}
//Turns out the window ordering stuff never gets hit with any of the current plugins.
//Leaving the following code here 'just in case' for the time being.
void LLCocoaPlugin::setupGroup()
{
// CreateWindowGroup(kWindowGroupAttrFixedLevel, &layer_group);
// if(layer_group)
// {
// // Start out with a window layer that's way out in front (fixes the problem with the menubar not getting hidden on first switch to fullscreen youtube)
// SetWindowGroupName(layer_group, CFSTR("SLPlugin Layer"));
// SetWindowGroupLevel(layer_group, kCGOverlayWindowLevel);
// }
}
void LLCocoaPlugin::updateWindows()
{
// NSArray* window_list = [NSApp orderedWindows];
// NSWindow* current_window = [window_list objectAtIndex:0];
// NSWindow* parent_window = [ current_window parentWindow ];
// bool this_is_front_process = false;
// bool parent_is_front_process = false;
//
//
// // Check for a change in this process's frontmost window.
// if ( current_window != mFrontWindow )
// {
// // and figure out whether this process or its parent are currently frontmost
// if ( current_window == parent_window ) parent_is_front_process = true;
// if ( current_window == mPluginWindow ) this_is_front_process = true;
//
// if (current_window != NULL && mFrontWindow == NULL)
// {
// // Opening the first window
//
// if(mHackState == 0)
// {
// // Next time through the event loop, lower the window group layer
// mHackState = 1;
// }
//
// if(parent_is_front_process)
// {
// // Bring this process's windows to the front.
// [mPluginWindow makeKeyAndOrderFront:NSApp];
// [mPluginWindow setOrderedIndex:0];
// }
//
// [NSApp activateIgnoringOtherApps:YES];
// }
//
// else if (( current_window == NULL) && (mFrontWindow != NULL))
// {
// // Closing the last window
//
// if(this_is_front_process)
// {
// // Try to bring this process's parent to the front
// [parent_window makeKeyAndOrderFront:NSApp];
// [parent_window setOrderedIndex:0];
// }
// }
// else if(mHackState == 1)
// {
//// if(layer_group)
//// {
//// // Set the window group level back to something less extreme
//// SetWindowGroupLevel(layer_group, kCGNormalWindowLevel);
//// }
// mHackState = 2;
// }
//
// mFrontWindow = [window_list objectAtIndex:0];
// }
}
+267
View File
@@ -0,0 +1,267 @@
/**
* @file slplugin.cpp
* @brief Loader shell for plugins, intended to be launched by the plugin host application, which directly loads a plugin dynamic library.
*
* @cond
*
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*
* @endcond
*/
#include "linden_common.h"
#include "llpluginprocesschild.h"
#include "llpluginmessage.h"
#include "llerrorcontrol.h"
#include "llapr.h"
#include "llstring.h"
#include <iostream>
#include <fstream>
using namespace std;
#if LL_DARWIN
#include "slplugin-objc.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 or LSUIElement flag in the Info.plist.
Normally non-bundled binaries don't have an info.plist file, but it's possible to embed one in the binary by adding this to the linker flags:
-sectcreate __TEXT __info_plist /path/to/slplugin_info.plist
which means adding this to the gcc flags:
-Wl,-sectcreate,__TEXT,__info_plist,/path/to/slplugin_info.plist
Now that SLPlugin is a bundled app on the Mac, this is no longer necessary (it can just use a regular Info.plist file), but I'm leaving this comment in for posterity.
*/
#if LL_DARWIN || LL_LINUX
// 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;
}
////////////////////////////////////////////////////////////////////////////////
// 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 );
}
bool checkExceptionHandler()
{
bool ok = true;
LPTOP_LEVEL_EXCEPTION_FILTER prev_filter;
prev_filter = SetUnhandledExceptionFilter(myWin32ExceptionHandler);
if (prev_filter != myWin32ExceptionHandler)
{
LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with " << prev_filter << "!" << LL_ENDL;
ok = false;
}
if (prev_filter == nullptr)
{
ok = false;
if (nullptr == myWin32ExceptionHandler)
{
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
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
# if LL_DARWIN
signal(SIGEMT, &crash_handler); // emulate instruction executed
LLCocoaPlugin cocoa_interface;
cocoa_interface.setupCocoa();
cocoa_interface.createAutoReleasePool();
#endif //LL_DARWIN
LLPluginProcessChild *plugin = new LLPluginProcessChild();
plugin->init(port);
#if LL_DARWIN
cocoa_interface.deleteAutoReleasePool();
#endif
LLTimer timer;
timer.start();
#if LL_WINDOWS
checkExceptionHandler();
#endif
#if LL_DARWIN
// If the plugin opens a new window (such as the Flash plugin's fullscreen player), we may need to bring this plugin process to the foreground.
// Use this to track the current frontmost window and bring this process to the front if it changes.
// cocoa_interface.mEventTarget = GetEventDispatcherTarget();
#endif
while(!plugin->isDone())
{
#if LL_DARWIN
cocoa_interface.createAutoReleasePool();
#endif
timer.reset();
plugin->idle();
#if LL_DARWIN
{
cocoa_interface.processEvents();
}
#endif
F64 elapsed = timer.getElapsedTimeF64();
F64 remaining = plugin->getSleepTime() - elapsed;
if(remaining <= 0.0)
{
// 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
#if LL_DARWIN
cocoa_interface.deleteAutoReleasePool();
#endif
}
delete plugin;
ll_cleanup_apr();
return 0;
}
@@ -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>LSUIElement</key>
<string>1</string>
</dict>
</plist>