/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSim Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.IO; using System.Runtime.Remoting; using System.Runtime.Remoting.Lifetime; using System.Threading; using System.Collections; using System.Collections.Generic; using System.Security.Policy; using System.Reflection; using System.Globalization; using System.Xml; using OpenMetaverse; using log4net; using Nini.Config; using Amib.Threading; using OpenSim.Framework; using OpenSim.Region.CoreModules; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.ScriptEngine.Shared; using OpenSim.Region.ScriptEngine.Shared.Api; using OpenSim.Region.ScriptEngine.Shared.Api.Runtime; using OpenSim.Region.ScriptEngine.Shared.ScriptBase; using OpenSim.Region.ScriptEngine.Shared.CodeTools; using OpenSim.Region.ScriptEngine.Interfaces; namespace OpenSim.Region.ScriptEngine.Shared.Instance { public class ScriptInstance : IScriptInstance { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private IScriptEngine m_Engine; private IScriptWorkItem m_CurrentResult = null; private Queue m_EventQueue = new Queue(32); private bool m_RunEvents = false; private UUID m_ItemID; private uint m_LocalID; private UUID m_ObjectID; private UUID m_AssetID; private IScript m_Script; private UUID m_AppDomain; private DetectParams[] m_DetectParams; private bool m_TimerQueued; private DateTime m_EventStart; private bool m_InEvent; private string m_PrimName; private string m_ScriptName; private string m_Assembly; private int m_StartParam = 0; private string m_CurrentEvent = String.Empty; private bool m_InSelfDelete = false; private int m_MaxScriptQueue; private bool m_SaveState = true; private bool m_ShuttingDown = false; private int m_ControlEventsInQueue = 0; private int m_LastControlLevel = 0; private bool m_CollisionInQueue = false; private TaskInventoryItem m_thisScriptTask; // The following is for setting a minimum delay between events private double m_minEventDelay = 0; private long m_eventDelayTicks = 0; private long m_nextEventTimeTicks = 0; private bool m_startOnInit = true; private StateSource m_stateSource; private bool m_postOnRez; private bool m_startedFromSavedState = false; //private ISponsor m_ScriptSponsor; private Dictionary, KeyValuePair> m_LineMap; public Dictionary, KeyValuePair> LineMap { get { return m_LineMap; } set { m_LineMap = value; } } private Dictionary m_Apis = new Dictionary(); // Script state private string m_State="default"; public Object[] PluginData = new Object[0]; /// /// Used by llMinEventDelay to suppress events happening any faster than this speed. /// This currently restricts all events in one go. Not sure if each event type has /// its own check so take the simple route first. /// public double MinEventDelay { get { return m_minEventDelay; } set { if (value > 0.001) m_minEventDelay = value; else m_minEventDelay = 0.0; m_eventDelayTicks = (long)(m_minEventDelay * 10000000L); m_nextEventTimeTicks = DateTime.Now.Ticks; } } public bool Running { get { return m_RunEvents; } set { m_RunEvents = value; } } public bool ShuttingDown { get { return m_ShuttingDown; } set { m_ShuttingDown = value; } } public string State { get { return m_State; } set { m_State = value; } } public IScriptEngine Engine { get { return m_Engine; } } public UUID AppDomain { get { return m_AppDomain; } set { m_AppDomain = value; } } public string PrimName { get { return m_PrimName; } } public string ScriptName { get { return m_ScriptName; } } public UUID ItemID { get { return m_ItemID; } } public UUID ObjectID { get { return m_ObjectID; } } public uint LocalID { get { return m_LocalID; } } public UUID AssetID { get { return m_AssetID; } } public Queue EventQueue { get { return m_EventQueue; } } public void ClearQueue() { m_TimerQueued = false; m_EventQueue.Clear(); } public int StartParam { get { return m_StartParam; } set { m_StartParam = value; } } public TaskInventoryItem ScriptTask { get { return m_thisScriptTask; } } public ScriptInstance(IScriptEngine engine, SceneObjectPart part, UUID itemID, UUID assetID, string assembly, AppDomain dom, string primName, string scriptName, int startParam, bool postOnRez, StateSource stateSource, int maxScriptQueue) { m_Engine = engine; m_LocalID = part.LocalId; m_ObjectID = part.UUID; m_ItemID = itemID; m_AssetID = assetID; m_PrimName = primName; m_ScriptName = scriptName; m_Assembly = assembly; m_StartParam = startParam; m_MaxScriptQueue = maxScriptQueue; m_stateSource = stateSource; m_postOnRez = postOnRez; if (part != null && part.TaskInventory.ContainsKey(m_ItemID)) { m_thisScriptTask = part.TaskInventory[m_ItemID]; } ApiManager am = new ApiManager(); foreach (string api in am.GetApis()) { m_Apis[api] = am.CreateApi(api); m_Apis[api].Initialize(engine, part, m_LocalID, itemID); } try { m_Script = (IScript)dom.CreateInstanceAndUnwrap( Path.GetFileNameWithoutExtension(assembly), "SecondLife.Script"); // Add a sponsor to the script // ISponsor scriptSponsor = new ScriptSponsor(); // ILease lease = (ILease)RemotingServices.GetLifetimeService(m_Script as MarshalByRefObject); // lease.Register(scriptSponsor); //m_ScriptSponsor = scriptSponsor; } catch (Exception e) { m_Engine.Log.ErrorFormat("[Script] Error loading assembly {0}\n"+e.ToString(), assembly); } try { foreach (KeyValuePair kv in m_Apis) { m_Script.InitApi(kv.Key, kv.Value); } // m_Engine.Log.Debug("[Script] Script instance created"); part.SetScriptEvents(m_ItemID, (int)m_Script.GetStateEventFlags(State)); } catch (Exception e) { m_Engine.Log.Error("[Script] Error loading script instance\n"+e.ToString()); return; } m_SaveState = true; string savedState = Path.Combine(Path.GetDirectoryName(assembly), m_ItemID.ToString() + ".state"); if (File.Exists(savedState)) { string xml = String.Empty; try { FileInfo fi = new FileInfo(savedState); int size=(int)fi.Length; if (size < 512000) { using (FileStream fs = File.Open(savedState, FileMode.Open, FileAccess.Read, FileShare.None)) { System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); Byte[] data = new Byte[size]; fs.Read(data, 0, size); xml = enc.GetString(data); ScriptSerializer.Deserialize(xml, this); AsyncCommandManager.CreateFromData(m_Engine, m_LocalID, m_ItemID, m_ObjectID, PluginData); // m_Engine.Log.DebugFormat("[Script] Successfully retrieved state for script {0}.{1}", m_PrimName, m_ScriptName); part.SetScriptEvents(m_ItemID, (int)m_Script.GetStateEventFlags(State)); if (m_RunEvents && (!m_ShuttingDown)) { m_RunEvents = false; } else { m_RunEvents = false; m_startOnInit = false; } // we get new rez events on sim restart, too // but if there is state, then we fire the change // event // We loaded state, don't force a re-save m_SaveState = false; m_startedFromSavedState = true; } } else { m_Engine.Log.Error("[Script] Unable to load script state: Memory limit exceeded"); } } catch (Exception e) { m_Engine.Log.ErrorFormat("[Script] Unable to load script state from xml: {0}\n"+e.ToString(), xml); } } else { ScenePresence presence = m_Engine.World.GetScenePresence(part.OwnerID); if (presence != null && (!postOnRez)) presence.ControllingClient.SendAgentAlertMessage("Compile successful", false); // m_Engine.Log.ErrorFormat("[Script] Unable to load script state, file not found"); } } public void Init() { if (!m_startOnInit) return; if (m_startedFromSavedState) { Start(); if (m_postOnRez) { PostEvent(new EventParams("on_rez", new Object[] {new LSL_Types.LSLInteger(m_StartParam)}, new DetectParams[0])); } if (m_stateSource == StateSource.NewRez) { // m_Engine.Log.Debug("[Script] Posted changed(CHANGED_REGION_RESTART) to script"); PostEvent(new EventParams("changed", new Object[] {new LSL_Types.LSLInteger(256)}, new DetectParams[0])); } else if (m_stateSource == StateSource.PrimCrossing) { // CHANGED_REGION PostEvent(new EventParams("changed", new Object[] {new LSL_Types.LSLInteger(512)}, new DetectParams[0])); } } else { Start(); PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); if (m_postOnRez) { PostEvent(new EventParams("on_rez", new Object[] {new LSL_Types.LSLInteger(m_StartParam)}, new DetectParams[0])); } } } private void ReleaseControls() { SceneObjectPart part=m_Engine.World.GetSceneObjectPart(m_LocalID); if (part != null && part.TaskInventory.ContainsKey(m_ItemID)) { UUID permsGranter = part.TaskInventory[m_ItemID].PermsGranter; int permsMask = part.TaskInventory[m_ItemID].PermsMask; if ((permsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0) { ScenePresence presence = m_Engine.World.GetScenePresence(permsGranter); if (presence != null) presence.UnRegisterControlEventsToScript(m_LocalID, m_ItemID); } } } public void DestroyScriptInstance() { ReleaseControls(); AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID); } public void RemoveState() { string savedState = Path.Combine(Path.GetDirectoryName(m_Assembly), m_ItemID.ToString() + ".state"); try { File.Delete(savedState); } catch(Exception) { } } public void VarDump(Dictionary vars) { Console.WriteLine("Variable dump for script {0}", m_ItemID.ToString()); foreach (KeyValuePair v in vars) { Console.WriteLine("Variable: {0} = '{1}'", v. Key, v.Value.ToString()); } } public void Start() { lock (m_EventQueue) { if (Running) return; m_RunEvents = true; if (m_EventQueue.Count > 0) { if (m_CurrentResult == null) m_CurrentResult = m_Engine.QueueEventHandler(this); else m_Engine.Log.Error("[Script] Tried to start a script that was already queued"); } } } public bool Stop(int timeout) { IScriptWorkItem result; lock (m_EventQueue) { if (!Running) return true; if (m_CurrentResult == null) { m_RunEvents = false; return true; } if (m_CurrentResult.Cancel()) { m_CurrentResult = null; m_RunEvents = false; return true; } result = m_CurrentResult; m_RunEvents = false; } if (result.Wait(new TimeSpan((long)timeout * 100000))) { return true; } lock (m_EventQueue) { result = m_CurrentResult; } if (result == null) return true; if (!m_InSelfDelete) result.Abort(); lock (m_EventQueue) { m_CurrentResult = null; } return true; } public void SetState(string state) { if (state == State) return; PostEvent(new EventParams("state_exit", new Object[0], new DetectParams[0])); PostEvent(new EventParams("state", new Object[] { state }, new DetectParams[0])); PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); throw new EventAbortException(); } public void PostEvent(EventParams data) { // m_Engine.Log.DebugFormat("[Script] Posted event {2} in state {3} to {0}.{1}", // m_PrimName, m_ScriptName, data.EventName, m_State); if (!Running) return; // If min event delay is set then ignore any events untill the time has expired // This currently only allows 1 event of any type in the given time period. // This may need extending to allow for a time for each individual event type. if (m_eventDelayTicks != 0) { if (DateTime.Now.Ticks < m_nextEventTimeTicks) return; m_nextEventTimeTicks = DateTime.Now.Ticks + m_eventDelayTicks; } lock (m_EventQueue) { if (m_EventQueue.Count >= m_MaxScriptQueue) return; if (data.EventName == "timer") { if (m_TimerQueued) return; m_TimerQueued = true; } if (data.EventName == "control") { int held = ((LSL_Types.LSLInteger)data.Params[1]).value; // int changed = ((LSL_Types.LSLInteger)data.Params[2]).value; // If the last message was a 0 (nothing held) // and this one is also nothing held, drop it // if (m_LastControlLevel == held && held == 0) return; // If there is one or more queued, then queue // only changed ones, else queue unconditionally // if (m_ControlEventsInQueue > 0) { if (m_LastControlLevel == held) return; } m_LastControlLevel = held; m_ControlEventsInQueue++; } if (data.EventName == "collision") { if (m_CollisionInQueue) return; if (data.DetectParams == null) return; m_CollisionInQueue = true; } m_EventQueue.Enqueue(data); if (m_CurrentResult == null) { m_CurrentResult = m_Engine.QueueEventHandler(this); } } } /// /// Process the next event queued for this script /// /// public object EventProcessor() { lock (m_Script) { EventParams data = null; lock (m_EventQueue) { data = (EventParams) m_EventQueue.Dequeue(); if (data == null) // Shouldn't happen { if ((m_EventQueue.Count > 0) && m_RunEvents && (!m_ShuttingDown)) { m_CurrentResult = m_Engine.QueueEventHandler(this); } else { m_CurrentResult = null; } return 0; } if (data.EventName == "timer") m_TimerQueued = false; if (data.EventName == "control") { if (m_ControlEventsInQueue > 0) m_ControlEventsInQueue--; } if (data.EventName == "collision") m_CollisionInQueue = false; } //m_log.DebugFormat("[XENGINE]: Processing event {0} for {1}", data.EventName, this); m_DetectParams = data.DetectParams; if (data.EventName == "state") // Hardcoded state change { // m_Engine.Log.DebugFormat("[Script] Script {0}.{1} state set to {2}", // m_PrimName, m_ScriptName, data.Params[0].ToString()); m_State=data.Params[0].ToString(); AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID); SceneObjectPart part = m_Engine.World.GetSceneObjectPart( m_LocalID); if (part != null) { part.SetScriptEvents(m_ItemID, (int)m_Script.GetStateEventFlags(State)); } } else { if (m_Engine.World.PipeEventsForScript(m_LocalID) || data.EventName == "control") // Don't freeze avies! { SceneObjectPart part = m_Engine.World.GetSceneObjectPart( m_LocalID); // m_Engine.Log.DebugFormat("[Script] Delivered event {2} in state {3} to {0}.{1}", // m_PrimName, m_ScriptName, data.EventName, m_State); try { m_CurrentEvent = data.EventName; m_EventStart = DateTime.Now; m_InEvent = true; m_Script.ExecuteEvent(State, data.EventName, data.Params); m_InEvent = false; m_CurrentEvent = String.Empty; if (m_SaveState) { // This will be the very first event we deliver // (state_entry) in default state // SaveState(m_Assembly); m_SaveState = false; } } catch (Exception e) { m_InEvent = false; m_CurrentEvent = String.Empty; if ((!(e is TargetInvocationException) || !(e.InnerException is SelfDeleteException)) && (!(e is ThreadAbortException))) { try { // DISPLAY ERROR INWORLD string text = FormatException(e); if (text.Length > 1000) text = text.Substring(0, 1000); m_Engine.World.SimChat(Utils.StringToBytes(text), ChatTypeEnum.DebugChannel, 2147483647, part.AbsolutePosition, part.Name, part.UUID, false); } catch (Exception e2) // LEGIT: User Scripting { m_Engine.Log.Error("[Script]: "+ "Error displaying error in-world: " + e2.ToString()); m_Engine.Log.Error("[Script]: " + "Errormessage: Error compiling script:\r\n" + e.ToString()); } } else if ((e is TargetInvocationException) && (e.InnerException is SelfDeleteException)) { m_InSelfDelete = true; if (part != null && part.ParentGroup != null) m_Engine.World.DeleteSceneObject(part.ParentGroup, false); } } } } lock (m_EventQueue) { if ((m_EventQueue.Count > 0) && m_RunEvents && (!m_ShuttingDown)) { m_CurrentResult = m_Engine.QueueEventHandler(this); } else { m_CurrentResult = null; } } m_DetectParams = null; return 0; } } public int EventTime() { if (!m_InEvent) return 0; return (DateTime.Now - m_EventStart).Seconds; } public void ResetScript() { if (m_Script == null) return; bool running = Running; RemoveState(); ReleaseControls(); Stop(0); SceneObjectPart part=m_Engine.World.GetSceneObjectPart(m_LocalID); part.Inventory.GetInventoryItem(m_ItemID).PermsMask = 0; part.Inventory.GetInventoryItem(m_ItemID).PermsGranter = UUID.Zero; AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID); m_EventQueue.Clear(); m_Script.ResetVars(); m_State = "default"; part.SetScriptEvents(m_ItemID, (int)m_Script.GetStateEventFlags(State)); if (running) Start(); m_SaveState = true; PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); } public void ApiResetScript() { // bool running = Running; RemoveState(); ReleaseControls(); m_Script.ResetVars(); SceneObjectPart part=m_Engine.World.GetSceneObjectPart(m_LocalID); part.Inventory.GetInventoryItem(m_ItemID).PermsMask = 0; part.Inventory.GetInventoryItem(m_ItemID).PermsGranter = UUID.Zero; AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID); m_EventQueue.Clear(); m_Script.ResetVars(); m_State = "default"; part.SetScriptEvents(m_ItemID, (int)m_Script.GetStateEventFlags(State)); if (m_CurrentEvent != "state_entry") { m_SaveState = true; PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); throw new EventAbortException(); } } public Dictionary GetVars() { return m_Script.GetVars(); } public void SetVars(Dictionary vars) { m_Script.SetVars(vars); } public DetectParams GetDetectParams(int idx) { if (m_DetectParams == null) return null; if (idx < 0 || idx >= m_DetectParams.Length) return null; return m_DetectParams[idx]; } public UUID GetDetectID(int idx) { if (m_DetectParams == null) return UUID.Zero; if (idx < 0 || idx >= m_DetectParams.Length) return UUID.Zero; return m_DetectParams[idx].Key; } public void SaveState(string assembly) { // If we're currently in an event, just tell it to save upon return // if (m_InEvent) { m_SaveState = true; return; } PluginData = AsyncCommandManager.GetSerializationData(m_Engine, m_ItemID); string xml = ScriptSerializer.Serialize(this); try { FileStream fs = File.Create(Path.Combine(Path.GetDirectoryName(assembly), m_ItemID.ToString() + ".state")); System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding(); Byte[] buf = enc.GetBytes(xml); fs.Write(buf, 0, buf.Length); fs.Close(); } catch(Exception e) { Console.WriteLine("Unable to save xml\n"+e.ToString()); } if (!File.Exists(Path.Combine(Path.GetDirectoryName(assembly), m_ItemID.ToString() + ".state"))) { throw new Exception("Completed persistence save, but no file was created"); } } public IScriptApi GetApi(string name) { if (m_Apis.ContainsKey(name)) return m_Apis[name]; return null; } public override string ToString() { return String.Format("{0} {1} on {2}", m_ScriptName, m_ItemID, m_PrimName); } string FormatException(Exception e) { if (e.InnerException == null) // Not a normal runtime error return e.ToString(); string message = "Runtime error:\n" + e.InnerException.StackTrace; string[] lines = message.Split(new char[] {'\n'}); foreach (string line in lines) { if (line.Contains("SecondLife.Script")) { int idx = line.IndexOf(':'); if (idx != -1) { string val = line.Substring(idx+1); int lineNum = 0; if (int.TryParse(val, out lineNum)) { KeyValuePair pos = Compiler.FindErrorPosition( lineNum, 0, LineMap); int scriptLine = pos.Key; int col = pos.Value; if (scriptLine == 0) scriptLine++; if (col == 0) col++; message = string.Format("Runtime error:\n" + "Line ({0}): {1}", scriptLine - 1, e.InnerException.Message); return message; } } } } m_log.ErrorFormat("Scripting exception:"); m_log.ErrorFormat(e.ToString()); return e.ToString(); } public string GetAssemblyName() { return m_Assembly; } public string GetXMLState() { bool run = Running; Stop(100); Running = run; // We should not be doing this, but since we are about to // dispose this, it really doesn't make a difference // This is meant to work around a Windows only race // m_InEvent = false; SaveState(m_Assembly); return ScriptSerializer.Serialize(this); } } }