From 85654f82a515df99d01dd2d2f3b619747a6cc5db Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 12:25:04 -0500 Subject: [PATCH 01/78] First cut of AutoBackupModule; only compile-tested so far --- .../Resources/OptionalModules.addin.xml | 1 + .../World/AutoBackup/AutoBackupModule.cs | 540 ++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100644 OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs diff --git a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml index 5eea286fd7..869134354f 100644 --- a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml +++ b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml @@ -13,5 +13,6 @@ + diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs new file mode 100644 index 0000000000..ed21e41c58 --- /dev/null +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -0,0 +1,540 @@ +/* + * 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 OpenSimulator 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.Timers; +using System.Diagnostics; +using System.Reflection; +using System.Collections.Generic; +using log4net; +using Nini; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + + +/* + * Config Settings Documentation. + * EACH REGION in e.g. Regions/Regions.ini can have the following options: + * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. + * This is the only required option for enabling auto-backup; the other options have sane defaults. + * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. + * AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). + * The number of minutes between each backup attempt. + * If a negative or zero value is given, it is equivalent to setting AutoBackup = False. + * AutoBackupBusyCheck: True/False. Default: True. + * If True, we will only take an auto-backup if a set of conditions are met. + * These conditions are heuristics to try and avoid taking a backup when the sim is busy. + * AutoBackupScript: String. Default: not specified (disabled). + * File path to an executable script or binary to run when an automatic backup is taken. + * The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. + * Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! + * argv[1] of the executed file/script will be the file name of the generated OAR. + * If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. + * AutoBackupNaming: string. Default: Time. + * One of three strings (case insensitive): + * "Time": Current timestamp is appended to file name. An existing file will never be overwritten. + * "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. + * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. + * AutoBackupDir: String. Default: "." (the current directory). + * A directory (absolute or relative) where backups should be saved. + * */ + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + + public enum NamingType + { + TIME, + SEQUENTIAL, + OVERWRITE + }; + + public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase + { + + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + //AutoBackupModuleState: Auto-Backup state for one region (scene). + public class AutoBackupModuleState + { + private readonly IScene m_scene; + private bool m_enabled = false; + private NamingType m_naming = NamingType.TIME; + private Timer m_timer = null; + private bool m_busycheck = true; + private string m_script = null; + private string m_dir = "."; + + public AutoBackupModuleState(IScene scene) + { + m_scene = scene; + if(scene == null) + throw new NullReferenceException("Required parameter missing for AutoBackupModuleState constructor"); + } + + public void SetEnabled(bool b) + { + m_enabled = b; + } + + public bool GetEnabled() + { + return m_enabled; + } + + public Timer GetTimer() + { + return m_timer; + } + + public void SetTimer(Timer t) + { + m_timer = t; + } + + public bool GetBusyCheck() + { + return m_busycheck; + } + + public void SetBusyCheck(bool b) + { + m_busycheck = b; + } + + + public string GetScript() + { + return m_script; + } + + public void SetScript(string s) + { + m_script = s; + } + + public string GetBackupDir() + { + return m_dir; + } + + public void SetBackupDir(string s) + { + m_dir = s; + } + + public NamingType GetNamingType() + { + return m_naming; + } + + public void SetNamingType(NamingType n) + { + m_naming = n; + } + } + + //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. + //Also helps if you don't want AutoBackup at all + readonly Dictionary states = new Dictionary(4); + readonly Dictionary timers = new Dictionary(1); + readonly Dictionary> timerMap = new Dictionary>(1); + + public AutoBackupModule () + { + + } + + #region IRegionModuleBase implementation + void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) + { + //I have no overall config settings to care about. + } + + void IRegionModuleBase.Close () + { + //We don't want any timers firing while the sim's coming down; strange things may happen. + StopAllTimers(); + } + + void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) + { + //NO-OP. Wait for the region to be loaded. + } + + void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) + { + AutoBackupModuleState abms = states[scene]; + Timer timer = abms.GetTimer(); + List list = timerMap[timer]; + list.Remove(scene); + if(list.Count == 0) + { + timerMap.Remove(timer); + timers.Remove(timer.Interval); + timer.Close(); + } + } + + void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) + { + //This really ought not to happen, but just in case, let's pretend it didn't... + if(scene == null) + return; + + AutoBackupModuleState st = new AutoBackupModuleState(scene); + states.Add(scene, st); + + //Read the config settings and set variables. + IConfig config = scene.Config.Configs[scene.RegionInfo.RegionName]; + st.SetEnabled(config.GetBoolean("AutoBackup", false)); + if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. + return; + + //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. + double interval = config.GetDouble("AutoBackupInterval", 720); + if(timers.ContainsKey(interval)) + { + st.SetTimer(timers[interval]); + } + else + { + st.SetTimer(new Timer(interval)); + timers.Add(interval, st.GetTimer()); + st.GetTimer().Elapsed += HandleElapsed; + } + + //Add the current region to the list of regions tied to this timer. + if(timerMap.ContainsKey(st.GetTimer())) + { + timerMap[st.GetTimer()].Add(scene); + } + else + { + List scns = new List(1); + timerMap.Add(st.GetTimer(), scns); + } + + st.SetBusyCheck(config.GetBoolean("AutoBackupBusyCheck", true)); + + //Set file naming algorithm + string namingtype = config.GetString("AutoBackupNaming", "Time"); + if(namingtype.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) + { + st.SetNamingType(NamingType.TIME); + } + else if(namingtype.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) + { + st.SetNamingType(NamingType.SEQUENTIAL); + } + else if(namingtype.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) + { + st.SetNamingType(NamingType.OVERWRITE); + } + else + { + m_log.Warn("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); + st.SetNamingType(NamingType.TIME); + } + + st.SetScript(config.GetString("AutoBackupScript", null)); + st.SetBackupDir(config.GetString("AutoBackupDir", ".")); + + //Let's give the user *one* convenience and auto-mkdir + if(st.GetBackupDir() != ".") + { + try + { + DirectoryInfo dirinfo = new DirectoryInfo(st.GetBackupDir()); + if(!dirinfo.Exists) + { + dirinfo.Create(); + } + } + catch(Exception e) + { + m_log.Warn("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir() + + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); + } + } + } + + void HandleElapsed (object sender, ElapsedEventArgs e) + { + bool heuristicsRun = false; + bool heuristicsPassed = false; + foreach(IScene scene in timerMap[(Timer)sender]) + { + AutoBackupModuleState state = states[scene]; + bool heuristics = state.GetBusyCheck(); + + //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. + if((heuristics && heuristicsRun && heuristicsPassed) + || !heuristics) + { + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + if(savePath == null) + { + m_log.Warn("savePath is null in HandleElapsed"); + continue; + } + iram.ArchiveRegion(savePath, null); + ExecuteScript(state.GetScript(), savePath); + } + //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + else if(heuristics && heuristicsRun && !heuristicsPassed) + { + continue; + } + //Logical Deduction: heuristics are on but haven't been run + else + { + heuristicsPassed = RunHeuristics(); + heuristicsRun = true; + if(!heuristicsPassed) + continue; + } + } + } + + string IRegionModuleBase.Name { + get { + return "AutoBackupModule"; + } + } + + Type IRegionModuleBase.ReplaceableInterface { + get { + return null; + } + } + + #endregion + #region ISharedRegionModule implementation + void ISharedRegionModule.PostInitialise () + { + //I don't care right now. + } + + #endregion + + //Is this even needed? + public bool IsSharedModule + { + get { return true; } + } + + private string BuildOarPath(string regionName, string baseDir, NamingType naming) + { + FileInfo path = null; + switch(naming) + { + case NamingType.OVERWRITE: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName); + return path.FullName; + case NamingType.TIME: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString() + ".oar"); + return path.FullName; + case NamingType.SEQUENTIAL: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + "_" + GetNextFile(baseDir, regionName) + ".oar"); + return path.FullName; + default: + m_log.Warn("VERY BAD: Unhandled case element " + naming.ToString()); + break; + } + + return path.FullName; + } + + //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. + //(Terrible reference to ) + //This format may turn out to be too unwieldy to keep... + //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? + //Sequential numbers, right? Ugh. Almost makes TOO much sense. + private string GetTimeString() + { + StringWriter sw = new StringWriter(); + sw.Write("_"); + DateTime now = DateTime.Now; + sw.Write(now.Year); + sw.Write("y_"); + sw.Write(now.Month); + sw.Write("M_"); + sw.Write(now.Day); + sw.Write("d_"); + sw.Write(now.Hour); + sw.Write("h_"); + sw.Write(now.Minute); + sw.Write("m_"); + sw.Write(now.Second); + sw.Write("s"); + sw.Flush(); + string output = sw.ToString(); + sw.Close(); + return output; + } + + //Get the next logical file name + //I really shouldn't put fields here, but for now.... ;) + private string m_dirName = null; + private string m_regionName = null; + private string GetNextFile(string dirName, string regionName) + { + FileInfo uniqueFile = null; + m_dirName = dirName; + m_regionName = regionName; + long biggestExistingFile = HalfIntervalMaximize(1, FileExistsTest); + biggestExistingFile++; //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + + uniqueFile = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); + if(uniqueFile.Exists) + { + //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! + //Now you get to pay the performance cost :) + uniqueFile = UniqueFileSearchLinear(biggestExistingFile); + } + + return uniqueFile.FullName; + } + + private bool RunHeuristics() + { + return true; + } + + private void ExecuteScript(string scriptName, string savePath) + { + //Fast path out + if(scriptName == null || scriptName.Length <= 0) + return; + + try + { + FileInfo fi = new FileInfo(scriptName); + if(fi.Exists) + { + ProcessStartInfo psi = new ProcessStartInfo(scriptName); + psi.Arguments = savePath; + psi.CreateNoWindow = true; + Process proc = Process.Start(psi); + proc.ErrorDataReceived += HandleProcErrorDataReceived; + } + } + catch(Exception e) + { + m_log.Warn("Exception encountered when trying to run script for oar backup " + savePath, e); + } + } + + void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) + { + m_log.Warn("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); + } + + private void StopAllTimers() + { + foreach(Timer t in timerMap.Keys) + { + t.Close(); + } + } + + /* Find the largest value for which the predicate returns true. + * We use a bisection algorithm (half interval) to make the algorithm scalable. + * The worst-case complexity is about O(log(n)^2) in practice. + * Only for extremely small values (under 10) do you notice it taking more iterations than a linear search. + * The number of predicate invocations only hits a few hundred when the maximized value + * is in the tens of millions, so prepare for the predicate to be invoked between 10 and 100 times. + * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. + * The Predicate parameter must be a function that accepts a long and returns a bool. + * */ + public long HalfIntervalMaximize(long start, Predicate pred) + { + long prev = start, curr = start, biggest = 0; + + if(start < 0) + throw new IndexOutOfRangeException("Start value for HalfIntervalMaximize must be non-negative"); + + do + { + if(pred(curr)) + { + if(curr > biggest) + { + biggest = curr; + } + prev = curr; + if(curr == 0) + { + //Special case because 0 * 2 = 0 :) + curr = 1; + } + else + { + //Look deeper + curr *= 2; + } + } + else + { + // We went too far, back off halfway + curr = (curr + prev) / 2; + } + } + while(curr - prev > 0); + + return biggest; + } + + public bool FileExistsTest(long num) + { + FileInfo test = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); + return test.Exists; + } + + + //Very slow, hence why we try the HalfIntervalMaximize first! + public FileInfo UniqueFileSearchLinear(long start) + { + long l = start; + FileInfo retval = null; + do + { + retval = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); + } + while(retval.Exists); + + return retval; + } +} + +} + From dfa63ff0313610e1c2d262f7e660af94134159f6 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 20:15:38 -0500 Subject: [PATCH 02/78] Let GetNextFile do all the string-building work for SEQUENTIAL. --- .../Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index ed21e41c58..f8d9060ea1 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -364,7 +364,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString() + ".oar"); return path.FullName; case NamingType.SEQUENTIAL: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + "_" + GetNextFile(baseDir, regionName) + ".oar"); + path = new FileInfo(GetNextFile(baseDir, regionName)); return path.FullName; default: m_log.Warn("VERY BAD: Unhandled case element " + naming.ToString()); From 99e82602826e7d100d04a4bb229188be240db1ad Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:08:19 -0500 Subject: [PATCH 03/78] Add [Modules] option for unconditionally disabling entire module globally (for easy configuration) --- .../World/AutoBackup/AutoBackupModule.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index f8d9060ea1..7593b95e02 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -40,6 +40,9 @@ using OpenSim.Region.Framework.Interfaces; /* * Config Settings Documentation. + * At the TOP LEVEL, e.g. in OpenSim.ini, we have one option: + * In the [Modules] section: + * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! * EACH REGION in e.g. Regions/Regions.ini can have the following options: * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. * This is the only required option for enabling auto-backup; the other options have sane defaults. @@ -166,6 +169,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup readonly Dictionary states = new Dictionary(4); readonly Dictionary timers = new Dictionary(1); readonly Dictionary> timerMap = new Dictionary>(1); + private bool m_Enabled = false; //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! public AutoBackupModule () { @@ -175,11 +179,24 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #region IRegionModuleBase implementation void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) { - //I have no overall config settings to care about. + //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + IConfig moduleConfig = source.Configs["Modules"]; + if (moduleConfig != null) + { + m_Enabled = moduleConfig.GetBoolean("AutoBackupModule", false); + if (m_Enabled) + { + m_log.Info("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); + } + + } } void IRegionModuleBase.Close () { + if(!m_Enabled) + return; + //We don't want any timers firing while the sim's coming down; strange things may happen. StopAllTimers(); } @@ -191,6 +208,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) { + if(!m_Enabled) + return; + AutoBackupModuleState abms = states[scene]; Timer timer = abms.GetTimer(); List list = timerMap[timer]; @@ -205,6 +225,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) { + if(!m_Enabled) + return; + //This really ought not to happen, but just in case, let's pretend it didn't... if(scene == null) return; From 06a4810d210fcbfe1a859317698f791129ecdaf1 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:29:45 -0500 Subject: [PATCH 04/78] Fix config source by taking it from Initialize --- .../OptionalModules/World/AutoBackup/AutoBackupModule.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 7593b95e02..68cf2192c6 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -169,6 +169,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup readonly Dictionary states = new Dictionary(4); readonly Dictionary timers = new Dictionary(1); readonly Dictionary> timerMap = new Dictionary>(1); + private IConfigSource m_configSource = null; private bool m_Enabled = false; //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! public AutoBackupModule () @@ -180,6 +181,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) { //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + m_configSource = source; IConfig moduleConfig = source.Configs["Modules"]; if (moduleConfig != null) { @@ -236,7 +238,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup states.Add(scene, st); //Read the config settings and set variables. - IConfig config = scene.Config.Configs[scene.RegionInfo.RegionName]; + IConfig config = m_configSource.Configs[scene.RegionInfo.RegionName]; st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. return; From 3c9bf5c476eb294748b53e0c65e7880c26c108ab Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:47:05 -0500 Subject: [PATCH 05/78] Fix config source stuff harder (debug console prints only; no fix yet) --- .../World/AutoBackup/AutoBackupModule.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 68cf2192c6..ebad12ffc0 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -234,11 +234,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(scene == null) return; + m_log.Info("[AUTO BACKUP MODULE]: RegionLoaded for region: " + scene.RegionInfo.RegionName); + AutoBackupModuleState st = new AutoBackupModuleState(scene); states.Add(scene, st); //Read the config settings and set variables. IConfig config = m_configSource.Configs[scene.RegionInfo.RegionName]; + if(config == null) + { + m_log.Warn("[AUTO BACKUP MODULE]: Can't get config settings! Here are the IConfigs available:"); + foreach(IConfig c in m_configSource.Configs) + { + m_log.Warn("[AUTO BACKUP MODULE]: " + c.Name); + } + throw new NullReferenceException("This is debug code"); + } st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. return; From e5c08a553c4cbf0485d7eb93fc891e002ae04254 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:55:32 -0500 Subject: [PATCH 06/78] More debug messages --- .../World/AutoBackup/AutoBackupModule.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index ebad12ffc0..21e48df736 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -248,6 +248,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { m_log.Warn("[AUTO BACKUP MODULE]: " + c.Name); } + + if(scene.Config != null) + { + m_log.Warn("[AUTO BACKUP MODULE]: And in scene.Config:"); + IConfigSource tmp = scene.Config; + foreach(IConfig d in tmp.Configs) + { + m_log.Warn("[AUTO BACKUP MODULE]: " + d.Name); + } + } throw new NullReferenceException("This is debug code"); } st.SetEnabled(config.GetBoolean("AutoBackup", false)); From 7eac0af5590a450752a9fd2c57abe7d1a6d84c4c Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 23:07:54 -0500 Subject: [PATCH 07/78] Fix an actual bug in the timer interval calculation (minutes -> msec) --- .../World/AutoBackup/AutoBackupModule.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 21e48df736..489971814c 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -240,7 +240,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup states.Add(scene, st); //Read the config settings and set variables. - IConfig config = m_configSource.Configs[scene.RegionInfo.RegionName]; + IConfig config = scene.Config.Configs["Startup"]; if(config == null) { m_log.Warn("[AUTO BACKUP MODULE]: Can't get config settings! Here are the IConfigs available:"); @@ -258,11 +258,17 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Warn("[AUTO BACKUP MODULE]: " + d.Name); } } - throw new NullReferenceException("This is debug code"); + throw new NullReferenceException("This is debug code"); //This crashes the whole process -- not good } st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. + { return; + } + else + { + m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is AutoBackup ENABLED."); + } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. double interval = config.GetDouble("AutoBackupInterval", 720); @@ -272,7 +278,13 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } else { - st.SetTimer(new Timer(interval)); + //0 or negative interval == do nothing. + if(interval <= 0.0) + { + st.SetEnabled(false); + return; + } + st.SetTimer(new Timer(interval * 60000)); //Milliseconds -> minutes timers.Add(interval, st.GetTimer()); st.GetTimer().Elapsed += HandleElapsed; } From c2658c2f14d75c0c90f40c3673eb35677bcf2cb7 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 23:14:43 -0500 Subject: [PATCH 08/78] More relevant console messages, and maybe fix config problem --- .../World/AutoBackup/AutoBackupModule.cs | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 489971814c..4735620da7 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -234,35 +234,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(scene == null) return; - m_log.Info("[AUTO BACKUP MODULE]: RegionLoaded for region: " + scene.RegionInfo.RegionName); - AutoBackupModuleState st = new AutoBackupModuleState(scene); states.Add(scene, st); //Read the config settings and set variables. - IConfig config = scene.Config.Configs["Startup"]; + IConfig config = scene.Config.Configs["AutoBackupModule"]; if(config == null) { - m_log.Warn("[AUTO BACKUP MODULE]: Can't get config settings! Here are the IConfigs available:"); - foreach(IConfig c in m_configSource.Configs) - { - m_log.Warn("[AUTO BACKUP MODULE]: " + c.Name); - } - - if(scene.Config != null) - { - m_log.Warn("[AUTO BACKUP MODULE]: And in scene.Config:"); - IConfigSource tmp = scene.Config; - foreach(IConfig d in tmp.Configs) - { - m_log.Warn("[AUTO BACKUP MODULE]: " + d.Name); - } - } - throw new NullReferenceException("This is debug code"); //This crashes the whole process -- not good + //No config settings for this, let's just give up. + st.SetEnabled(false); + m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); + return; } st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. { + m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); return; } else From 2da9bb3ca2fbd4294409b74733f30b79bd48e2ec Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:31:10 -0500 Subject: [PATCH 09/78] Try to fix config one more time. Note that the way we specify settings has changed significantly here. --- .../World/AutoBackup/AutoBackupModule.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 4735620da7..0869b0c523 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -40,10 +40,13 @@ using OpenSim.Region.Framework.Interfaces; /* * Config Settings Documentation. - * At the TOP LEVEL, e.g. in OpenSim.ini, we have one option: + * At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: * In the [Modules] section: * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! - * EACH REGION in e.g. Regions/Regions.ini can have the following options: + * EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. + * VERY IMPORTANT: You must create the key name as follows: . + * Example: My region is named Foo. + * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. * This is the only required option for enabling auto-backup; the other options have sane defaults. * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. @@ -190,7 +193,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { m_log.Info("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); } - } } @@ -234,19 +236,20 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(scene == null) return; + string sRegionName = scene.RegionInfo.RegionName; AutoBackupModuleState st = new AutoBackupModuleState(scene); states.Add(scene, st); //Read the config settings and set variables. - IConfig config = scene.Config.Configs["AutoBackupModule"]; + IConfig config = m_configSource.Configs["AutoBackupModule"]; if(config == null) { - //No config settings for this, let's just give up. + //No config settings for any regions, let's just give up. st.SetEnabled(false); m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); return; } - st.SetEnabled(config.GetBoolean("AutoBackup", false)); + st.SetEnabled(config.GetBoolean(sRegionName + ".AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. { m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); @@ -258,7 +261,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble("AutoBackupInterval", 720); + double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720); if(timers.ContainsKey(interval)) { st.SetTimer(timers[interval]); @@ -287,10 +290,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup timerMap.Add(st.GetTimer(), scns); } - st.SetBusyCheck(config.GetBoolean("AutoBackupBusyCheck", true)); + st.SetBusyCheck(config.GetBoolean(sRegionName + ".AutoBackupBusyCheck", true)); //Set file naming algorithm - string namingtype = config.GetString("AutoBackupNaming", "Time"); + string namingtype = config.GetString(sRegionName + ".AutoBackupNaming", "Time"); if(namingtype.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) { st.SetNamingType(NamingType.TIME); @@ -309,8 +312,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetNamingType(NamingType.TIME); } - st.SetScript(config.GetString("AutoBackupScript", null)); - st.SetBackupDir(config.GetString("AutoBackupDir", ".")); + st.SetScript(config.GetString(sRegionName + ".AutoBackupScript", null)); + st.SetBackupDir(config.GetString(sRegionName + ".AutoBackupDir", ".")); //Let's give the user *one* convenience and auto-mkdir if(st.GetBackupDir() != ".") From 7fa8ed0c47b3b0a5c87cd27b2620aeef05a7fe86 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:48:02 -0500 Subject: [PATCH 10/78] Config works, but timer isn't firing. Hmm. --- .../World/AutoBackup/AutoBackupModule.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 0869b0c523..68c8301651 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -246,25 +246,26 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { //No config settings for any regions, let's just give up. st.SetEnabled(false); - m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); + m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; } st.SetEnabled(config.GetBoolean(sRegionName + ".AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. { - m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); + m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; } else { - m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is AutoBackup ENABLED."); + m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720); + double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720) * 60000; if(timers.ContainsKey(interval)) { st.SetTimer(timers[interval]); + m_log.Debug("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); } else { @@ -274,9 +275,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetEnabled(false); return; } - st.SetTimer(new Timer(interval * 60000)); //Milliseconds -> minutes + st.SetTimer(new Timer(interval)); //Milliseconds -> minutes timers.Add(interval, st.GetTimer()); st.GetTimer().Elapsed += HandleElapsed; + m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. From bb1f03abc6a3c12b4f511e4f212f5673c4d31dc4 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:57:48 -0500 Subject: [PATCH 11/78] Make timer auto-respawn. --- .../OptionalModules/World/AutoBackup/AutoBackupModule.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 68c8301651..58b93c05ef 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -275,9 +275,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetEnabled(false); return; } - st.SetTimer(new Timer(interval)); //Milliseconds -> minutes - timers.Add(interval, st.GetTimer()); - st.GetTimer().Elapsed += HandleElapsed; + Timer tim = new Timer(interval); + st.SetTimer(tim); //Milliseconds -> minutes + timers.Add(interval, tim); + tim.Elapsed += HandleElapsed; + tim.AutoReset = True; m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } @@ -351,6 +353,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { IRegionArchiverModule iram = scene.RequestModuleInterface(); string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); if(savePath == null) { m_log.Warn("savePath is null in HandleElapsed"); From cb049aa15f64fec5ba4b08aedddf98b68ab5044e Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:58:42 -0500 Subject: [PATCH 12/78] s/True/true --- .../Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 58b93c05ef..ba5e24bf87 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -279,7 +279,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetTimer(tim); //Milliseconds -> minutes timers.Add(interval, tim); tim.Elapsed += HandleElapsed; - tim.AutoReset = True; + tim.AutoReset = true; m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } From d3511ca592eff780e6d707680ac1d20a24b9fc58 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 09:03:11 -0500 Subject: [PATCH 13/78] Start the timer. (Could it be that simple?) --- .../Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index ba5e24bf87..77126c7fb2 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -280,6 +280,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup timers.Add(interval, tim); tim.Elapsed += HandleElapsed; tim.AutoReset = true; + tim.Start(); m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } From 34b6904939dd551aafa68ea8e02dc5177d395b3b Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 26 Feb 2011 22:09:19 -0500 Subject: [PATCH 14/78] First working commit of AutoBackupModule. It seems to do something! Heuristics are still TODO, but this is alpha 1. --- .../World/AutoBackup/AutoBackupModule.cs | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 77126c7fb2..e3686acc35 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -281,7 +281,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup tim.Elapsed += HandleElapsed; tim.AutoReset = true; tim.Start(); - m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); + //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. @@ -292,6 +292,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup else { List scns = new List(1); + scns.Add(scene); timerMap.Add(st.GetTimer(), scns); } @@ -343,6 +344,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { bool heuristicsRun = false; bool heuristicsPassed = false; + if(!timerMap.ContainsKey((Timer) sender)) + { + m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender.ToString()); + } foreach(IScene scene in timerMap[(Timer)sender]) { AutoBackupModuleState state = states[scene]; @@ -352,16 +357,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { - IRegionArchiverModule iram = scene.RequestModuleInterface(); - string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); - m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); - if(savePath == null) - { - m_log.Warn("savePath is null in HandleElapsed"); - continue; - } - iram.ArchiveRegion(savePath, null); - ExecuteScript(state.GetScript(), savePath); + doRegionBackup(scene); } //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! else if(heuristics && heuristicsRun && !heuristicsPassed) @@ -375,9 +371,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup heuristicsRun = true; if(!heuristicsPassed) continue; + doRegionBackup(scene); } } } + + void doRegionBackup(IScene scene) + { + AutoBackupModuleState state = states[scene]; + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); + if(savePath == null) + { + m_log.Warn("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); + return; + } + iram.ArchiveRegion(savePath, null); + ExecuteScript(state.GetScript(), savePath); + } string IRegionModuleBase.Name { get { From a01c44e74de669ea2643b8bfe76a7e78ca4740a4 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 28 Feb 2011 11:04:54 -0500 Subject: [PATCH 15/78] Be smarter about stopping timers. Cleanup formatting. Use a boolean flag to tell timers that fire after IRegionModuleBase.Close() is called that they should not execute. Also, I used MonoDevelop's auto-formatting feature to format the code uniformly. No guarantee about variable names though. --- .../World/AutoBackup/AutoBackupModule.cs | 502 ++++++++---------- 1 file changed, 225 insertions(+), 277 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index e3686acc35..54b9b09cb3 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -73,20 +73,19 @@ using OpenSim.Region.Framework.Interfaces; namespace OpenSim.Region.OptionalModules.World.AutoBackup { - + public enum NamingType { TIME, SEQUENTIAL, OVERWRITE - }; - + } + public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase { - - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - + + private static readonly ILog m_log = LogManager.GetLogger (MethodBase.GetCurrentMethod ().DeclaringType); + //AutoBackupModuleState: Auto-Backup state for one region (scene). public class AutoBackupModuleState { @@ -97,84 +96,87 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private bool m_busycheck = true; private string m_script = null; private string m_dir = "."; - - public AutoBackupModuleState(IScene scene) + + public AutoBackupModuleState (IScene scene) { m_scene = scene; - if(scene == null) - throw new NullReferenceException("Required parameter missing for AutoBackupModuleState constructor"); + if (scene == null) + throw new NullReferenceException ("Required parameter missing for AutoBackupModuleState constructor"); } - - public void SetEnabled(bool b) + + public void SetEnabled (bool b) { - m_enabled = b; + m_enabled = b; } - - public bool GetEnabled() + + public bool GetEnabled () { - return m_enabled; + return m_enabled; } - - public Timer GetTimer() + + public Timer GetTimer () { - return m_timer; + return m_timer; } - - public void SetTimer(Timer t) + + public void SetTimer (Timer t) { - m_timer = t; + m_timer = t; } - - public bool GetBusyCheck() + + public bool GetBusyCheck () { - return m_busycheck; + return m_busycheck; } - - public void SetBusyCheck(bool b) + + public void SetBusyCheck (bool b) { - m_busycheck = b; + m_busycheck = b; } - - - public string GetScript() + + + public string GetScript () { - return m_script; + return m_script; } - - public void SetScript(string s) + + public void SetScript (string s) { - m_script = s; + m_script = s; } - - public string GetBackupDir() + + public string GetBackupDir () { - return m_dir; + return m_dir; } - - public void SetBackupDir(string s) + + public void SetBackupDir (string s) { - m_dir = s; + m_dir = s; } - - public NamingType GetNamingType() + + public NamingType GetNamingType () { return m_naming; } - - public void SetNamingType(NamingType n) + + public void SetNamingType (NamingType n) { - m_naming = n; + m_naming = n; } } - + //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. //Also helps if you don't want AutoBackup at all - readonly Dictionary states = new Dictionary(4); - readonly Dictionary timers = new Dictionary(1); - readonly Dictionary> timerMap = new Dictionary>(1); + readonly Dictionary states = new Dictionary (4); + readonly Dictionary timers = new Dictionary (1); + readonly Dictionary> timerMap = new Dictionary> (1); private IConfigSource m_configSource = null; - private bool m_Enabled = false; //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! - + private bool m_Enabled = false; + //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! + private bool m_closed = false; + //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. + //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. public AutoBackupModule () { @@ -185,24 +187,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module m_configSource = source; - IConfig moduleConfig = source.Configs["Modules"]; - if (moduleConfig != null) - { - m_Enabled = moduleConfig.GetBoolean("AutoBackupModule", false); - if (m_Enabled) - { - m_log.Info("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); - } - } + IConfig moduleConfig = source.Configs["Modules"]; + if (moduleConfig != null) { + m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); + if (m_Enabled) { + m_log.Info ("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); + } + } } void IRegionModuleBase.Close () { - if(!m_Enabled) + if (!m_Enabled) return; //We don't want any timers firing while the sim's coming down; strange things may happen. - StopAllTimers(); + StopAllTimers (); } void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) @@ -212,327 +212,286 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) { - if(!m_Enabled) + if (!m_Enabled) return; AutoBackupModuleState abms = states[scene]; - Timer timer = abms.GetTimer(); + Timer timer = abms.GetTimer (); List list = timerMap[timer]; - list.Remove(scene); - if(list.Count == 0) - { - timerMap.Remove(timer); - timers.Remove(timer.Interval); - timer.Close(); + list.Remove (scene); + if (list.Count == 0) { + timerMap.Remove (timer); + timers.Remove (timer.Interval); + timer.Close (); } } void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) { - if(!m_Enabled) + if (!m_Enabled) return; //This really ought not to happen, but just in case, let's pretend it didn't... - if(scene == null) + if (scene == null) return; string sRegionName = scene.RegionInfo.RegionName; - AutoBackupModuleState st = new AutoBackupModuleState(scene); - states.Add(scene, st); + AutoBackupModuleState st = new AutoBackupModuleState (scene); + states.Add (scene, st); //Read the config settings and set variables. IConfig config = m_configSource.Configs["AutoBackupModule"]; - if(config == null) - { + if (config == null) { //No config settings for any regions, let's just give up. - st.SetEnabled(false); - m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); + st.SetEnabled (false); + m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; } - st.SetEnabled(config.GetBoolean(sRegionName + ".AutoBackup", false)); - if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. - { - m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); + st.SetEnabled (config.GetBoolean (sRegionName + ".AutoBackup", false)); + //If you don't want AutoBackup, we stop. + if (!st.GetEnabled ()) { + m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; - } - else - { - m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); + } else { + m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720) * 60000; - if(timers.ContainsKey(interval)) - { - st.SetTimer(timers[interval]); - m_log.Debug("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); - } - else - { + double interval = config.GetDouble (sRegionName + ".AutoBackupInterval", 720) * 60000; + if (timers.ContainsKey (interval)) { + st.SetTimer (timers[interval]); + m_log.Debug ("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); + } else { //0 or negative interval == do nothing. - if(interval <= 0.0) - { - st.SetEnabled(false); + if (interval <= 0.0) { + st.SetEnabled (false); return; } - Timer tim = new Timer(interval); - st.SetTimer(tim); //Milliseconds -> minutes - timers.Add(interval, tim); - tim.Elapsed += HandleElapsed; + Timer tim = new Timer (interval); + st.SetTimer (tim); + //Milliseconds -> minutes + timers.Add (interval, tim); + tim.Elapsed += HandleElapsed; tim.AutoReset = true; - tim.Start(); + tim.Start (); //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. - if(timerMap.ContainsKey(st.GetTimer())) - { - timerMap[st.GetTimer()].Add(scene); - } - else - { - List scns = new List(1); - scns.Add(scene); - timerMap.Add(st.GetTimer(), scns); + if (timerMap.ContainsKey (st.GetTimer ())) { + timerMap[st.GetTimer ()].Add (scene); + } else { + List scns = new List (1); + scns.Add (scene); + timerMap.Add (st.GetTimer (), scns); } - st.SetBusyCheck(config.GetBoolean(sRegionName + ".AutoBackupBusyCheck", true)); + st.SetBusyCheck (config.GetBoolean (sRegionName + ".AutoBackupBusyCheck", true)); //Set file naming algorithm - string namingtype = config.GetString(sRegionName + ".AutoBackupNaming", "Time"); - if(namingtype.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) - { - st.SetNamingType(NamingType.TIME); - } - else if(namingtype.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) - { - st.SetNamingType(NamingType.SEQUENTIAL); - } - else if(namingtype.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) - { - st.SetNamingType(NamingType.OVERWRITE); - } - else - { - m_log.Warn("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); - st.SetNamingType(NamingType.TIME); + string namingtype = config.GetString (sRegionName + ".AutoBackupNaming", "Time"); + if (namingtype.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { + st.SetNamingType (NamingType.TIME); + } else if (namingtype.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { + st.SetNamingType (NamingType.SEQUENTIAL); + } else if (namingtype.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { + st.SetNamingType (NamingType.OVERWRITE); + } else { + m_log.Warn ("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); + st.SetNamingType (NamingType.TIME); } - st.SetScript(config.GetString(sRegionName + ".AutoBackupScript", null)); - st.SetBackupDir(config.GetString(sRegionName + ".AutoBackupDir", ".")); + st.SetScript (config.GetString (sRegionName + ".AutoBackupScript", null)); + st.SetBackupDir (config.GetString (sRegionName + ".AutoBackupDir", ".")); //Let's give the user *one* convenience and auto-mkdir - if(st.GetBackupDir() != ".") - { - try - { - DirectoryInfo dirinfo = new DirectoryInfo(st.GetBackupDir()); - if(!dirinfo.Exists) - { - dirinfo.Create(); + if (st.GetBackupDir () != ".") { + try { + DirectoryInfo dirinfo = new DirectoryInfo (st.GetBackupDir ()); + if (!dirinfo.Exists) { + dirinfo.Create (); } - } - catch(Exception e) - { - m_log.Warn("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir() + - " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); + } catch (Exception e) { + m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); } } } void HandleElapsed (object sender, ElapsedEventArgs e) { + if (m_closed) + return; bool heuristicsRun = false; bool heuristicsPassed = false; - if(!timerMap.ContainsKey((Timer) sender)) - { - m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender.ToString()); + if (!timerMap.ContainsKey ((Timer)sender)) { + m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); } - foreach(IScene scene in timerMap[(Timer)sender]) - { + foreach (IScene scene in timerMap[(Timer)sender]) { AutoBackupModuleState state = states[scene]; - bool heuristics = state.GetBusyCheck(); + bool heuristics = state.GetBusyCheck (); //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. - if((heuristics && heuristicsRun && heuristicsPassed) - || !heuristics) - { - doRegionBackup(scene); - } + if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { + doRegionBackup (scene); //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! - else if(heuristics && heuristicsRun && !heuristicsPassed) - { + } else if (heuristics && heuristicsRun && !heuristicsPassed) { continue; - } //Logical Deduction: heuristics are on but haven't been run - else - { - heuristicsPassed = RunHeuristics(); + } else { + heuristicsPassed = RunHeuristics (); heuristicsRun = true; - if(!heuristicsPassed) + if (!heuristicsPassed) continue; - doRegionBackup(scene); + doRegionBackup (scene); } } } - - void doRegionBackup(IScene scene) + + void doRegionBackup (IScene scene) { AutoBackupModuleState state = states[scene]; - IRegionArchiverModule iram = scene.RequestModuleInterface(); - string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + IRegionArchiverModule iram = scene.RequestModuleInterface (); + string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); - if(savePath == null) - { - m_log.Warn("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); + if (savePath == null) { + m_log.Warn ("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); return; } - iram.ArchiveRegion(savePath, null); - ExecuteScript(state.GetScript(), savePath); + iram.ArchiveRegion (savePath, null); + ExecuteScript (state.GetScript (), savePath); } string IRegionModuleBase.Name { - get { - return "AutoBackupModule"; - } + get { return "AutoBackupModule"; } } Type IRegionModuleBase.ReplaceableInterface { - get { - return null; - } + get { return null; } } - + #endregion #region ISharedRegionModule implementation void ISharedRegionModule.PostInitialise () { //I don't care right now. } - + #endregion - + //Is this even needed? - public bool IsSharedModule - { - get { return true; } - } - - private string BuildOarPath(string regionName, string baseDir, NamingType naming) + public bool IsSharedModule { + get { return true; } + } + + private string BuildOarPath (string regionName, string baseDir, NamingType naming) { FileInfo path = null; - switch(naming) - { + switch (naming) { case NamingType.OVERWRITE: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName); + path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName); return path.FullName; case NamingType.TIME: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString() + ".oar"); + path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString () + ".oar"); return path.FullName; case NamingType.SEQUENTIAL: - path = new FileInfo(GetNextFile(baseDir, regionName)); + path = new FileInfo (GetNextFile (baseDir, regionName)); return path.FullName; default: - m_log.Warn("VERY BAD: Unhandled case element " + naming.ToString()); + m_log.Warn ("VERY BAD: Unhandled case element " + naming.ToString ()); break; } return path.FullName; } - + //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. //(Terrible reference to ) //This format may turn out to be too unwieldy to keep... //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? //Sequential numbers, right? Ugh. Almost makes TOO much sense. - private string GetTimeString() + private string GetTimeString () { - StringWriter sw = new StringWriter(); - sw.Write("_"); + StringWriter sw = new StringWriter (); + sw.Write ("_"); DateTime now = DateTime.Now; - sw.Write(now.Year); - sw.Write("y_"); - sw.Write(now.Month); - sw.Write("M_"); - sw.Write(now.Day); - sw.Write("d_"); - sw.Write(now.Hour); - sw.Write("h_"); - sw.Write(now.Minute); - sw.Write("m_"); - sw.Write(now.Second); - sw.Write("s"); - sw.Flush(); - string output = sw.ToString(); - sw.Close(); + sw.Write (now.Year); + sw.Write ("y_"); + sw.Write (now.Month); + sw.Write ("M_"); + sw.Write (now.Day); + sw.Write ("d_"); + sw.Write (now.Hour); + sw.Write ("h_"); + sw.Write (now.Minute); + sw.Write ("m_"); + sw.Write (now.Second); + sw.Write ("s"); + sw.Flush (); + string output = sw.ToString (); + sw.Close (); return output; } - + //Get the next logical file name //I really shouldn't put fields here, but for now.... ;) private string m_dirName = null; private string m_regionName = null; - private string GetNextFile(string dirName, string regionName) + private string GetNextFile (string dirName, string regionName) { FileInfo uniqueFile = null; m_dirName = dirName; m_regionName = regionName; - long biggestExistingFile = HalfIntervalMaximize(1, FileExistsTest); - biggestExistingFile++; //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. - - uniqueFile = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); - if(uniqueFile.Exists) - { + long biggestExistingFile = HalfIntervalMaximize (1, FileExistsTest); + biggestExistingFile++; + //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + uniqueFile = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); + if (uniqueFile.Exists) { //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! //Now you get to pay the performance cost :) - uniqueFile = UniqueFileSearchLinear(biggestExistingFile); + uniqueFile = UniqueFileSearchLinear (biggestExistingFile); } return uniqueFile.FullName; } - - private bool RunHeuristics() + + private bool RunHeuristics () { return true; } - - private void ExecuteScript(string scriptName, string savePath) + + private void ExecuteScript (string scriptName, string savePath) { //Fast path out - if(scriptName == null || scriptName.Length <= 0) + if (scriptName == null || scriptName.Length <= 0) return; - try - { - FileInfo fi = new FileInfo(scriptName); - if(fi.Exists) - { - ProcessStartInfo psi = new ProcessStartInfo(scriptName); + try { + FileInfo fi = new FileInfo (scriptName); + if (fi.Exists) { + ProcessStartInfo psi = new ProcessStartInfo (scriptName); psi.Arguments = savePath; psi.CreateNoWindow = true; - Process proc = Process.Start(psi); + Process proc = Process.Start (psi); proc.ErrorDataReceived += HandleProcErrorDataReceived; } - } - catch(Exception e) - { - m_log.Warn("Exception encountered when trying to run script for oar backup " + savePath, e); + } catch (Exception e) { + m_log.Warn ("Exception encountered when trying to run script for oar backup " + savePath, e); } } void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) { - m_log.Warn("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); + m_log.Warn ("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); } - - private void StopAllTimers() + + private void StopAllTimers () { - foreach(Timer t in timerMap.Keys) - { - t.Close(); + foreach (Timer t in timerMap.Keys) { + t.Close (); } + m_closed = true; } - + /* Find the largest value for which the predicate returns true. * We use a bisection algorithm (half interval) to make the algorithm scalable. * The worst-case complexity is about O(log(n)^2) in practice. @@ -542,65 +501,54 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. * The Predicate parameter must be a function that accepts a long and returns a bool. * */ - public long HalfIntervalMaximize(long start, Predicate pred) + public long HalfIntervalMaximize (long start, Predicate pred) { long prev = start, curr = start, biggest = 0; - if(start < 0) - throw new IndexOutOfRangeException("Start value for HalfIntervalMaximize must be non-negative"); + if (start < 0) + throw new IndexOutOfRangeException ("Start value for HalfIntervalMaximize must be non-negative"); - do - { - if(pred(curr)) - { - if(curr > biggest) - { + do { + if (pred (curr)) { + if (curr > biggest) { biggest = curr; } prev = curr; - if(curr == 0) - { + if (curr == 0) { //Special case because 0 * 2 = 0 :) curr = 1; - } - else - { + } else { //Look deeper curr *= 2; } - } - else - { + } else { // We went too far, back off halfway curr = (curr + prev) / 2; } - } - while(curr - prev > 0); + } while (curr - prev > 0); return biggest; } - - public bool FileExistsTest(long num) + + public bool FileExistsTest (long num) { - FileInfo test = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); + FileInfo test = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); return test.Exists; } - - + + //Very slow, hence why we try the HalfIntervalMaximize first! - public FileInfo UniqueFileSearchLinear(long start) + public FileInfo UniqueFileSearchLinear (long start) { long l = start; FileInfo retval = null; - do - { - retval = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); - } - while(retval.Exists); + do { + retval = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); + } while (retval.Exists); return retval; } -} + } } From 018645f9f843d1e807a69a63b7dd82c294885eff Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 28 Feb 2011 11:45:50 -0500 Subject: [PATCH 16/78] First pass at busy heuristics. Compile-tested only. --- .../World/AutoBackup/AutoBackupModule.cs | 69 +++++++++++++++++-- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 54b9b09cb3..98127b720c 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -35,7 +35,9 @@ using log4net; using Nini; using Nini.Config; using OpenSim.Framework; +using OpenSim.Framework.Statistics; using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; /* @@ -319,6 +321,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void HandleElapsed (object sender, ElapsedEventArgs e) { + //TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region + //XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to + //check whether the region is too busy! Especially on sims with LOTS of regions. + //Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, + // but would allow us to be semantically correct while being easier on perf. + //Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... if (m_closed) return; bool heuristicsRun = false; @@ -333,15 +341,18 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { doRegionBackup (scene); - //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! } else if (heuristics && heuristicsRun && !heuristicsPassed) { + m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; - //Logical Deduction: heuristics are on but haven't been run + //Logical Deduction: heuristics are on but haven't been run } else { - heuristicsPassed = RunHeuristics (); + heuristicsPassed = RunHeuristics (scene); heuristicsRun = true; - if (!heuristicsPassed) + if (!heuristicsPassed) { + m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; + } doRegionBackup (scene); } } @@ -349,6 +360,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void doRegionBackup (IScene scene) { + if (scene.RegionStatus != RegionStatus.Up) { + //We won't backup a region that isn't operating normally. + m_log.Warn ("[AUTO BACKUP MODULE]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); + return; + } + AutoBackupModuleState state = states[scene]; IRegionArchiverModule iram = scene.RequestModuleInterface (); string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); @@ -454,9 +471,49 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return uniqueFile.FullName; } - private bool RunHeuristics () + /* + * Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. + * */ + private bool RunHeuristics (IScene region) { - return true; + try { + return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); + } catch (Exception e) { + m_log.Warn ("[AUTO BACKUP MODULE]: Exception in RunHeuristics", e); + return false; + } + } + + /* + * If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), + * then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). + * AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". + * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + * */ + private bool RunTimeDilationHeuristic (IScene region) + { + string regionName = region.RegionInfo.RegionName; + return region.TimeDilation >= m_configSource.Configs["AutoBackupModule"].GetFloat (regionName + ".AutoBackupDilationThreshold", 0.5f); + } + + /* + * If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), + * then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). + * AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". + * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + * */ + private bool RunAgentLimitHeuristic (IScene region) + { + string regionName = region.RegionInfo.RegionName; + try { + Scene scene = (Scene)region; + //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... + return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); + } catch (InvalidCastException ice) { + m_log.Debug ("[AUTO BACKUP MODULE]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); + return true; + //Non-obstructionist safest answer... + } } private void ExecuteScript (string scriptName, string savePath) From b3c42e952f24a5c280a3691e1f75912e21c77323 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 28 Feb 2011 11:54:07 -0500 Subject: [PATCH 17/78] Fix small bug with remove region; update settings docs. Our impl of IRegionModuleBase.RemoveRegion didn't remove the scene from the states map. --- .../World/AutoBackup/AutoBackupModule.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 98127b720c..766034256b 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -71,6 +71,10 @@ using OpenSim.Region.Framework.Scenes; * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. * AutoBackupDir: String. Default: "." (the current directory). * A directory (absolute or relative) where backups should be saved. + * AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. + * If the time dilation is below this value, don't take a backup right now. + * AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. + * If the number of agents is greater than this value, don't take a backup right now. * */ namespace OpenSim.Region.OptionalModules.World.AutoBackup @@ -91,7 +95,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //AutoBackupModuleState: Auto-Backup state for one region (scene). public class AutoBackupModuleState { - private readonly IScene m_scene; private bool m_enabled = false; private NamingType m_naming = NamingType.TIME; private Timer m_timer = null; @@ -99,11 +102,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private string m_script = null; private string m_dir = "."; - public AutoBackupModuleState (IScene scene) + public AutoBackupModuleState () { - m_scene = scene; - if (scene == null) - throw new NullReferenceException ("Required parameter missing for AutoBackupModuleState constructor"); + } public void SetEnabled (bool b) @@ -226,6 +227,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup timers.Remove (timer.Interval); timer.Close (); } + states.Remove(scene); } void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) @@ -238,7 +240,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return; string sRegionName = scene.RegionInfo.RegionName; - AutoBackupModuleState st = new AutoBackupModuleState (scene); + AutoBackupModuleState st = new AutoBackupModuleState (); states.Add (scene, st); //Read the config settings and set variables. From 4974a1ce69fb3a1d2937c7de7ba93079a918eb3a Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 11 Apr 2011 12:34:26 -0400 Subject: [PATCH 18/78] AutoBackup: Support region-independent settings too. --- .../World/AutoBackup/AutoBackupModule.cs | 234 +++++++++++++----- 1 file changed, 176 insertions(+), 58 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 766034256b..37a2d97ac3 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -49,6 +49,8 @@ using OpenSim.Region.Framework.Scenes; * VERY IMPORTANT: You must create the key name as follows: . * Example: My region is named Foo. * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. + * Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. + * Region-specific settings take precedence. * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. * This is the only required option for enabling auto-backup; the other options have sane defaults. * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. @@ -121,6 +123,18 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { return m_timer; } + + public double GetIntervalMinutes () + { + if(m_timer == null) + { + return -1.0; + } + else + { + return m_timer.Interval / 60000.0; + } + } public void SetTimer (Timer t) { @@ -167,6 +181,19 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { m_naming = n; } + + public string ToString() + { + string retval = ""; + + retval += "[AUTO BACKUP]: AutoBackup: " + (GetEnabled() ? "ENABLED" : "DISABLED") + "\n"; + retval += "[AUTO BACKUP]: Interval: " + GetIntervalMinutes() + " minutes" + "\n"; + retval += "[AUTO BACKUP]: Do Busy Check: " + (GetBusyCheck() ? "Yes" : "No") + "\n"; + retval += "[AUTO BACKUP]: Naming Type: " + GetNamingType().ToString() + "\n"; + retval += "[AUTO BACKUP]: Backup Dir: " + GetBackupDir() + "\n"; + retval += "[AUTO BACKUP]: Script: " + GetScript() + "\n"; + return retval; + } } //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. @@ -180,6 +207,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private bool m_closed = false; //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. + readonly AutoBackupModuleState defaultState = new AutoBackupModuleState(); + public AutoBackupModule () { @@ -194,9 +223,20 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (moduleConfig != null) { m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); if (m_Enabled) { - m_log.Info ("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); + m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); } } + + Timer defTimer = new Timer(720 * 60000); + defaultState.SetTimer(defTimer); + timers.Add (720*60000, defTimer); + defTimer.Elapsed += HandleElapsed; + defTimer.AutoReset = true; + defTimer.Start (); + + AutoBackupModuleState abms = ParseConfig(null, false); + m_log.Debug("[AUTO BACKUP]: Config for default"); + m_log.Debug(abms.ToString()); } void IRegionModuleBase.Close () @@ -238,87 +278,162 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //This really ought not to happen, but just in case, let's pretend it didn't... if (scene == null) return; - - string sRegionName = scene.RegionInfo.RegionName; - AutoBackupModuleState st = new AutoBackupModuleState (); - states.Add (scene, st); - - //Read the config settings and set variables. + + AutoBackupModuleState abms = ParseConfig(scene, true); + m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); + m_log.Debug(abms.ToString()); + } + + AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) + { + string sRegionName; + string sRegionLabel; + string prepend; + AutoBackupModuleState state; + + if(parseDefault) + { + sRegionName = null; + sRegionLabel = "DEFAULT"; + prepend = ""; + state = defaultState; + } + else + { + sRegionName = scene.RegionInfo.RegionName; + sRegionLabel = sRegionName; + prepend = sRegionName + "."; + state = null; + } + + //Read the config settings and set variables. IConfig config = m_configSource.Configs["AutoBackupModule"]; if (config == null) { - //No config settings for any regions, let's just give up. - st.SetEnabled (false); - m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); - return; + state = defaultState; //defaultState would be disabled too if the section doesn't exist. + m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; } - st.SetEnabled (config.GetBoolean (sRegionName + ".AutoBackup", false)); + + bool tmpEnabled = config.GetBoolean (prepend + "AutoBackup", defaultState.GetEnabled()); + if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state + { + state = new AutoBackupModuleState(); + state.SetEnabled (tmpEnabled); + } + //If you don't want AutoBackup, we stop. - if (!st.GetEnabled ()) { - m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); - return; + if ((state == null && !defaultState.GetEnabled()) || !state.GetEnabled ()) { + m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; } else { - m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); + m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble (sRegionName + ".AutoBackupInterval", 720) * 60000; + double interval = config.GetDouble (prepend + "AutoBackupInterval", defaultState.GetIntervalMinutes()) * 60000.0; + if(state == null && interval != defaultState.GetIntervalMinutes() * 60000.0) + { + state = new AutoBackupModuleState(); + } + if (timers.ContainsKey (interval)) { - st.SetTimer (timers[interval]); - m_log.Debug ("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); + if(state != null) + state.SetTimer (timers[interval]); + m_log.Debug ("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + sRegionLabel); } else { //0 or negative interval == do nothing. - if (interval <= 0.0) { - st.SetEnabled (false); - return; + if (interval <= 0.0 && state != null) { + state.SetEnabled (false); + return state; } Timer tim = new Timer (interval); - st.SetTimer (tim); + state.SetTimer (tim); //Milliseconds -> minutes timers.Add (interval, tim); tim.Elapsed += HandleElapsed; tim.AutoReset = true; tim.Start (); - //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); + //m_log.Debug("[AUTO BACKUP]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. - if (timerMap.ContainsKey (st.GetTimer ())) { - timerMap[st.GetTimer ()].Add (scene); + if (timerMap.ContainsKey (state.GetTimer ())) { + timerMap[state.GetTimer ()].Add (scene); } else { List scns = new List (1); scns.Add (scene); - timerMap.Add (st.GetTimer (), scns); + timerMap.Add (state.GetTimer (), scns); } - st.SetBusyCheck (config.GetBoolean (sRegionName + ".AutoBackupBusyCheck", true)); + bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); + if(state == null && tmpBusyCheck != defaultState.GetBusyCheck()) + { + state = new AutoBackupModuleState(); + } + + if(state != null) + { + state.SetBusyCheck (tmpBusyCheck); + } //Set file naming algorithm - string namingtype = config.GetString (sRegionName + ".AutoBackupNaming", "Time"); - if (namingtype.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { - st.SetNamingType (NamingType.TIME); - } else if (namingtype.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { - st.SetNamingType (NamingType.SEQUENTIAL); - } else if (namingtype.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { - st.SetNamingType (NamingType.OVERWRITE); + string stmpNamingType = config.GetString (prepend + "AutoBackupNaming", defaultState.GetNamingType().ToString()); + NamingType tmpNamingType; + if (stmpNamingType.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { + tmpNamingType = NamingType.TIME; + } else if (stmpNamingType.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { + tmpNamingType = NamingType.SEQUENTIAL; + } else if (stmpNamingType.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { + tmpNamingType = NamingType.OVERWRITE; } else { - m_log.Warn ("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); - st.SetNamingType (NamingType.TIME); + m_log.Warn ("Unknown naming type specified for region " + sRegionLabel + ": " + stmpNamingType); + tmpNamingType = NamingType.TIME; } - st.SetScript (config.GetString (sRegionName + ".AutoBackupScript", null)); - st.SetBackupDir (config.GetString (sRegionName + ".AutoBackupDir", ".")); - - //Let's give the user *one* convenience and auto-mkdir - if (st.GetBackupDir () != ".") { - try { - DirectoryInfo dirinfo = new DirectoryInfo (st.GetBackupDir ()); - if (!dirinfo.Exists) { - dirinfo.Create (); - } - } catch (Exception e) { - m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); - } + if(state == null && tmpNamingType != defaultState.GetNamingType()) + { + state = new AutoBackupModuleState(); } + + if(state != null) + { + state.SetNamingType(tmpNamingType); + } + + string tmpScript = config.GetString (prepend + "AutoBackupScript", defaultState.GetScript()); + if(state == null && tmpScript != defaultState.GetScript()) + { + state = new AutoBackupModuleState(); + } + + if(state != null) + { + state.SetScript (tmpScript); + } + + string tmpBackupDir = config.GetString (prepend + "AutoBackupDir", "."); + if(state == null && tmpBackupDir != defaultState.GetBackupDir()) + { + state = new AutoBackupModuleState(); + } + + if(state != null) + { + state.SetBackupDir (tmpBackupDir); + //Let's give the user *one* convenience and auto-mkdir + if (state.GetBackupDir () != ".") { + try { + DirectoryInfo dirinfo = new DirectoryInfo (state.GetBackupDir ()); + if (!dirinfo.Exists) { + dirinfo.Create (); + } + } catch (Exception e) { + m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + state.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); + } + } + } + + return state; } void HandleElapsed (object sender, ElapsedEventArgs e) @@ -336,7 +451,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (!timerMap.ContainsKey ((Timer)sender)) { m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); } - foreach (IScene scene in timerMap[(Timer)sender]) { + + List tmap = timerMap[(Timer)sender]; + if(tmap != null && tmap.Count > 0) + foreach (IScene scene in tmap) { AutoBackupModuleState state = states[scene]; bool heuristics = state.GetBusyCheck (); @@ -345,14 +463,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup doRegionBackup (scene); //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! } else if (heuristics && heuristicsRun && !heuristicsPassed) { - m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); + m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; //Logical Deduction: heuristics are on but haven't been run } else { heuristicsPassed = RunHeuristics (scene); heuristicsRun = true; if (!heuristicsPassed) { - m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); + m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; } doRegionBackup (scene); @@ -364,16 +482,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { if (scene.RegionStatus != RegionStatus.Up) { //We won't backup a region that isn't operating normally. - m_log.Warn ("[AUTO BACKUP MODULE]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); + m_log.Warn ("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); return; } AutoBackupModuleState state = states[scene]; IRegionArchiverModule iram = scene.RequestModuleInterface (); string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); - //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); + //m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); if (savePath == null) { - m_log.Warn ("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); + m_log.Warn ("[AUTO BACKUP]: savePath is null in HandleElapsed"); return; } iram.ArchiveRegion (savePath, null); @@ -481,7 +599,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup try { return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); } catch (Exception e) { - m_log.Warn ("[AUTO BACKUP MODULE]: Exception in RunHeuristics", e); + m_log.Warn ("[AUTO BACKUP]: Exception in RunHeuristics", e); return false; } } @@ -512,7 +630,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); } catch (InvalidCastException ice) { - m_log.Debug ("[AUTO BACKUP MODULE]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); + m_log.Debug ("[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); return true; //Non-obstructionist safest answer... } From 4ad05fb01d038b31738438d02850972e79739f30 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 11 Apr 2011 13:12:26 -0400 Subject: [PATCH 19/78] Fix some NREs on certain paths. --- .../World/AutoBackup/AutoBackupModule.cs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 37a2d97ac3..3d156ca076 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -324,6 +324,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //If you don't want AutoBackup, we stop. if ((state == null && !defaultState.GetEnabled()) || !state.GetEnabled ()) { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + state = defaultState; return state; } else { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); @@ -346,6 +347,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.SetEnabled (false); return state; } + if(state == null) + state = new AutoBackupModuleState(); Timer tim = new Timer (interval); state.SetTimer (tim); //Milliseconds -> minutes @@ -357,12 +360,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } //Add the current region to the list of regions tied to this timer. - if (timerMap.ContainsKey (state.GetTimer ())) { - timerMap[state.GetTimer ()].Add (scene); - } else { - List scns = new List (1); - scns.Add (scene); - timerMap.Add (state.GetTimer (), scns); + if(state != null) + { + if (timerMap.ContainsKey (state.GetTimer ())) { + timerMap[state.GetTimer ()].Add (scene); + } else { + List scns = new List (1); + scns.Add (scene); + timerMap.Add (state.GetTimer (), scns); + } + } + else + { + if(timerMap.ContainsKey(defaultState.GetTimer())) { + timerMap[defaultState.GetTimer()].Add(scene); + } else { + List scns = new List (1); + scns.Add(scene); + timerMap.Add(defaultState.GetTimer(), scns); + } } bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); From 95a6ee0a3793b72deb29fe6e853d59afc2ca631e Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 11 Apr 2011 13:20:46 -0400 Subject: [PATCH 20/78] Fix NREs harder. --- .../World/AutoBackup/AutoBackupModule.cs | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 3d156ca076..e52e9cb5f7 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -225,6 +225,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (m_Enabled) { m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); } + else { + m_log.Info ("[AUTO BACKUP]: AutoBackupModule disabled"); + return; + } } Timer defTimer = new Timer(720 * 60000); @@ -360,24 +364,27 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } //Add the current region to the list of regions tied to this timer. - if(state != null) + if(scene != null) { - if (timerMap.ContainsKey (state.GetTimer ())) { - timerMap[state.GetTimer ()].Add (scene); - } else { - List scns = new List (1); - scns.Add (scene); - timerMap.Add (state.GetTimer (), scns); + if(state != null) + { + if (timerMap.ContainsKey (state.GetTimer ())) { + timerMap[state.GetTimer ()].Add (scene); + } else { + List scns = new List (1); + scns.Add (scene); + timerMap.Add (state.GetTimer (), scns); + } } - } - else - { - if(timerMap.ContainsKey(defaultState.GetTimer())) { - timerMap[defaultState.GetTimer()].Add(scene); - } else { - List scns = new List (1); - scns.Add(scene); - timerMap.Add(defaultState.GetTimer(), scns); + else + { + if(timerMap.ContainsKey(defaultState.GetTimer())) { + timerMap[defaultState.GetTimer()].Add(scene); + } else { + List scns = new List (1); + scns.Add(scene); + timerMap.Add(defaultState.GetTimer(), scns); + } } } From 049bce4d003f6854868306c7d98f7b5a501cb047 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Tue, 12 Apr 2011 01:14:21 -0400 Subject: [PATCH 21/78] Fixup the global defaults config parsing code. --- .../World/AutoBackup/AutoBackupModule.cs | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index e52e9cb5f7..364697b3f1 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -238,7 +238,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup defTimer.AutoReset = true; defTimer.Start (); - AutoBackupModuleState abms = ParseConfig(null, false); + AutoBackupModuleState abms = ParseConfig(null, true); m_log.Debug("[AUTO BACKUP]: Config for default"); m_log.Debug(abms.ToString()); } @@ -262,16 +262,23 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (!m_Enabled) return; - AutoBackupModuleState abms = states[scene]; - Timer timer = abms.GetTimer (); - List list = timerMap[timer]; - list.Remove (scene); - if (list.Count == 0) { - timerMap.Remove (timer); - timers.Remove (timer.Interval); - timer.Close (); + if(states.ContainsKey(scene)) + { + AutoBackupModuleState abms = states[scene]; + + //Remove this scene out of the timer map list + Timer timer = abms.GetTimer (); + List list = timerMap[timer]; + list.Remove (scene); + + //Shut down the timer if this was the last scene for the timer + if (list.Count == 0) { + timerMap.Remove (timer); + timers.Remove (timer.Interval); + timer.Close (); + } + states.Remove(scene); } - states.Remove(scene); } void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) @@ -283,9 +290,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (scene == null) return; - AutoBackupModuleState abms = ParseConfig(scene, true); + AutoBackupModuleState abms = ParseConfig(scene, false); m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); - m_log.Debug(abms.ToString()); + m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); } AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) @@ -322,13 +329,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state { state = new AutoBackupModuleState(); - state.SetEnabled (tmpEnabled); + } + + if(state != null) + { + state.SetEnabled (tmpEnabled); } //If you don't want AutoBackup, we stop. - if ((state == null && !defaultState.GetEnabled()) || !state.GetEnabled ()) { + if ((state == null && !defaultState.GetEnabled()) || (state != null && !state.GetEnabled ())) { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - state = defaultState; return state; } else { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); From 082fad6dd28513e38120c9d272aeed385de8208f Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 23 Apr 2011 18:29:13 -0400 Subject: [PATCH 22/78] Fix most issues raised by justincc: http://opensimulator.org/mantis/view.php?id=5440 --- .../World/AutoBackup/AutoBackupModule.cs | 1509 ++++++++--------- .../World/AutoBackup/AutoBackupModuleState.cs | 109 ++ bin/OpenSimDefaults.ini | 4 + 3 files changed, 858 insertions(+), 764 deletions(-) create mode 100644 OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 364697b3f1..a4dbea406a 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -1,764 +1,745 @@ -/* - * 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 OpenSimulator 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.Timers; -using System.Diagnostics; -using System.Reflection; -using System.Collections.Generic; -using log4net; -using Nini; -using Nini.Config; -using OpenSim.Framework; -using OpenSim.Framework.Statistics; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - - -/* - * Config Settings Documentation. - * At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: - * In the [Modules] section: - * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! - * EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. - * VERY IMPORTANT: You must create the key name as follows: . - * Example: My region is named Foo. - * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. - * Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. - * Region-specific settings take precedence. - * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. - * This is the only required option for enabling auto-backup; the other options have sane defaults. - * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. - * AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). - * The number of minutes between each backup attempt. - * If a negative or zero value is given, it is equivalent to setting AutoBackup = False. - * AutoBackupBusyCheck: True/False. Default: True. - * If True, we will only take an auto-backup if a set of conditions are met. - * These conditions are heuristics to try and avoid taking a backup when the sim is busy. - * AutoBackupScript: String. Default: not specified (disabled). - * File path to an executable script or binary to run when an automatic backup is taken. - * The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. - * Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! - * argv[1] of the executed file/script will be the file name of the generated OAR. - * If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. - * AutoBackupNaming: string. Default: Time. - * One of three strings (case insensitive): - * "Time": Current timestamp is appended to file name. An existing file will never be overwritten. - * "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. - * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. - * AutoBackupDir: String. Default: "." (the current directory). - * A directory (absolute or relative) where backups should be saved. - * AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. - * If the time dilation is below this value, don't take a backup right now. - * AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. - * If the number of agents is greater than this value, don't take a backup right now. - * */ - -namespace OpenSim.Region.OptionalModules.World.AutoBackup -{ - - public enum NamingType - { - TIME, - SEQUENTIAL, - OVERWRITE - } - - public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase - { - - private static readonly ILog m_log = LogManager.GetLogger (MethodBase.GetCurrentMethod ().DeclaringType); - - //AutoBackupModuleState: Auto-Backup state for one region (scene). - public class AutoBackupModuleState - { - private bool m_enabled = false; - private NamingType m_naming = NamingType.TIME; - private Timer m_timer = null; - private bool m_busycheck = true; - private string m_script = null; - private string m_dir = "."; - - public AutoBackupModuleState () - { - - } - - public void SetEnabled (bool b) - { - m_enabled = b; - } - - public bool GetEnabled () - { - return m_enabled; - } - - public Timer GetTimer () - { - return m_timer; - } - - public double GetIntervalMinutes () - { - if(m_timer == null) - { - return -1.0; - } - else - { - return m_timer.Interval / 60000.0; - } - } - - public void SetTimer (Timer t) - { - m_timer = t; - } - - public bool GetBusyCheck () - { - return m_busycheck; - } - - public void SetBusyCheck (bool b) - { - m_busycheck = b; - } - - - public string GetScript () - { - return m_script; - } - - public void SetScript (string s) - { - m_script = s; - } - - public string GetBackupDir () - { - return m_dir; - } - - public void SetBackupDir (string s) - { - m_dir = s; - } - - public NamingType GetNamingType () - { - return m_naming; - } - - public void SetNamingType (NamingType n) - { - m_naming = n; - } - - public string ToString() - { - string retval = ""; - - retval += "[AUTO BACKUP]: AutoBackup: " + (GetEnabled() ? "ENABLED" : "DISABLED") + "\n"; - retval += "[AUTO BACKUP]: Interval: " + GetIntervalMinutes() + " minutes" + "\n"; - retval += "[AUTO BACKUP]: Do Busy Check: " + (GetBusyCheck() ? "Yes" : "No") + "\n"; - retval += "[AUTO BACKUP]: Naming Type: " + GetNamingType().ToString() + "\n"; - retval += "[AUTO BACKUP]: Backup Dir: " + GetBackupDir() + "\n"; - retval += "[AUTO BACKUP]: Script: " + GetScript() + "\n"; - return retval; - } - } - - //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. - //Also helps if you don't want AutoBackup at all - readonly Dictionary states = new Dictionary (4); - readonly Dictionary timers = new Dictionary (1); - readonly Dictionary> timerMap = new Dictionary> (1); - private IConfigSource m_configSource = null; - private bool m_Enabled = false; - //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! - private bool m_closed = false; - //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. - //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. - readonly AutoBackupModuleState defaultState = new AutoBackupModuleState(); - - public AutoBackupModule () - { - - } - - #region IRegionModuleBase implementation - void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) - { - //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module - m_configSource = source; - IConfig moduleConfig = source.Configs["Modules"]; - if (moduleConfig != null) { - m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); - if (m_Enabled) { - m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); - } - else { - m_log.Info ("[AUTO BACKUP]: AutoBackupModule disabled"); - return; - } - } - - Timer defTimer = new Timer(720 * 60000); - defaultState.SetTimer(defTimer); - timers.Add (720*60000, defTimer); - defTimer.Elapsed += HandleElapsed; - defTimer.AutoReset = true; - defTimer.Start (); - - AutoBackupModuleState abms = ParseConfig(null, true); - m_log.Debug("[AUTO BACKUP]: Config for default"); - m_log.Debug(abms.ToString()); - } - - void IRegionModuleBase.Close () - { - if (!m_Enabled) - return; - - //We don't want any timers firing while the sim's coming down; strange things may happen. - StopAllTimers (); - } - - void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) - { - //NO-OP. Wait for the region to be loaded. - } - - void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) - { - if (!m_Enabled) - return; - - if(states.ContainsKey(scene)) - { - AutoBackupModuleState abms = states[scene]; - - //Remove this scene out of the timer map list - Timer timer = abms.GetTimer (); - List list = timerMap[timer]; - list.Remove (scene); - - //Shut down the timer if this was the last scene for the timer - if (list.Count == 0) { - timerMap.Remove (timer); - timers.Remove (timer.Interval); - timer.Close (); - } - states.Remove(scene); - } - } - - void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) - { - if (!m_Enabled) - return; - - //This really ought not to happen, but just in case, let's pretend it didn't... - if (scene == null) - return; - - AutoBackupModuleState abms = ParseConfig(scene, false); - m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); - m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); - } - - AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) - { - string sRegionName; - string sRegionLabel; - string prepend; - AutoBackupModuleState state; - - if(parseDefault) - { - sRegionName = null; - sRegionLabel = "DEFAULT"; - prepend = ""; - state = defaultState; - } - else - { - sRegionName = scene.RegionInfo.RegionName; - sRegionLabel = sRegionName; - prepend = sRegionName + "."; - state = null; - } - - //Read the config settings and set variables. - IConfig config = m_configSource.Configs["AutoBackupModule"]; - if (config == null) { - state = defaultState; //defaultState would be disabled too if the section doesn't exist. - m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } - - bool tmpEnabled = config.GetBoolean (prepend + "AutoBackup", defaultState.GetEnabled()); - if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetEnabled (tmpEnabled); - } - - //If you don't want AutoBackup, we stop. - if ((state == null && !defaultState.GetEnabled()) || (state != null && !state.GetEnabled ())) { - m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } else { - m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); - } - - //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble (prepend + "AutoBackupInterval", defaultState.GetIntervalMinutes()) * 60000.0; - if(state == null && interval != defaultState.GetIntervalMinutes() * 60000.0) - { - state = new AutoBackupModuleState(); - } - - if (timers.ContainsKey (interval)) { - if(state != null) - state.SetTimer (timers[interval]); - m_log.Debug ("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + sRegionLabel); - } else { - //0 or negative interval == do nothing. - if (interval <= 0.0 && state != null) { - state.SetEnabled (false); - return state; - } - if(state == null) - state = new AutoBackupModuleState(); - Timer tim = new Timer (interval); - state.SetTimer (tim); - //Milliseconds -> minutes - timers.Add (interval, tim); - tim.Elapsed += HandleElapsed; - tim.AutoReset = true; - tim.Start (); - //m_log.Debug("[AUTO BACKUP]: New timer for " + interval + " msec for region " + sRegionName); - } - - //Add the current region to the list of regions tied to this timer. - if(scene != null) - { - if(state != null) - { - if (timerMap.ContainsKey (state.GetTimer ())) { - timerMap[state.GetTimer ()].Add (scene); - } else { - List scns = new List (1); - scns.Add (scene); - timerMap.Add (state.GetTimer (), scns); - } - } - else - { - if(timerMap.ContainsKey(defaultState.GetTimer())) { - timerMap[defaultState.GetTimer()].Add(scene); - } else { - List scns = new List (1); - scns.Add(scene); - timerMap.Add(defaultState.GetTimer(), scns); - } - } - } - - bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); - if(state == null && tmpBusyCheck != defaultState.GetBusyCheck()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetBusyCheck (tmpBusyCheck); - } - - //Set file naming algorithm - string stmpNamingType = config.GetString (prepend + "AutoBackupNaming", defaultState.GetNamingType().ToString()); - NamingType tmpNamingType; - if (stmpNamingType.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { - tmpNamingType = NamingType.TIME; - } else if (stmpNamingType.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { - tmpNamingType = NamingType.SEQUENTIAL; - } else if (stmpNamingType.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { - tmpNamingType = NamingType.OVERWRITE; - } else { - m_log.Warn ("Unknown naming type specified for region " + sRegionLabel + ": " + stmpNamingType); - tmpNamingType = NamingType.TIME; - } - - if(state == null && tmpNamingType != defaultState.GetNamingType()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetNamingType(tmpNamingType); - } - - string tmpScript = config.GetString (prepend + "AutoBackupScript", defaultState.GetScript()); - if(state == null && tmpScript != defaultState.GetScript()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetScript (tmpScript); - } - - string tmpBackupDir = config.GetString (prepend + "AutoBackupDir", "."); - if(state == null && tmpBackupDir != defaultState.GetBackupDir()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetBackupDir (tmpBackupDir); - //Let's give the user *one* convenience and auto-mkdir - if (state.GetBackupDir () != ".") { - try { - DirectoryInfo dirinfo = new DirectoryInfo (state.GetBackupDir ()); - if (!dirinfo.Exists) { - dirinfo.Create (); - } - } catch (Exception e) { - m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + state.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); - } - } - } - - return state; - } - - void HandleElapsed (object sender, ElapsedEventArgs e) - { - //TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region - //XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to - //check whether the region is too busy! Especially on sims with LOTS of regions. - //Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, - // but would allow us to be semantically correct while being easier on perf. - //Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... - if (m_closed) - return; - bool heuristicsRun = false; - bool heuristicsPassed = false; - if (!timerMap.ContainsKey ((Timer)sender)) { - m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); - } - - List tmap = timerMap[(Timer)sender]; - if(tmap != null && tmap.Count > 0) - foreach (IScene scene in tmap) { - AutoBackupModuleState state = states[scene]; - bool heuristics = state.GetBusyCheck (); - - //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. - if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { - doRegionBackup (scene); - //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! - } else if (heuristics && heuristicsRun && !heuristicsPassed) { - m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); - continue; - //Logical Deduction: heuristics are on but haven't been run - } else { - heuristicsPassed = RunHeuristics (scene); - heuristicsRun = true; - if (!heuristicsPassed) { - m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); - continue; - } - doRegionBackup (scene); - } - } - } - - void doRegionBackup (IScene scene) - { - if (scene.RegionStatus != RegionStatus.Up) { - //We won't backup a region that isn't operating normally. - m_log.Warn ("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); - return; - } - - AutoBackupModuleState state = states[scene]; - IRegionArchiverModule iram = scene.RequestModuleInterface (); - string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); - //m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); - if (savePath == null) { - m_log.Warn ("[AUTO BACKUP]: savePath is null in HandleElapsed"); - return; - } - iram.ArchiveRegion (savePath, null); - ExecuteScript (state.GetScript (), savePath); - } - - string IRegionModuleBase.Name { - get { return "AutoBackupModule"; } - } - - Type IRegionModuleBase.ReplaceableInterface { - get { return null; } - } - - #endregion - #region ISharedRegionModule implementation - void ISharedRegionModule.PostInitialise () - { - //I don't care right now. - } - - #endregion - - //Is this even needed? - public bool IsSharedModule { - get { return true; } - } - - private string BuildOarPath (string regionName, string baseDir, NamingType naming) - { - FileInfo path = null; - switch (naming) { - case NamingType.OVERWRITE: - path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName); - return path.FullName; - case NamingType.TIME: - path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString () + ".oar"); - return path.FullName; - case NamingType.SEQUENTIAL: - path = new FileInfo (GetNextFile (baseDir, regionName)); - return path.FullName; - default: - m_log.Warn ("VERY BAD: Unhandled case element " + naming.ToString ()); - break; - } - - return path.FullName; - } - - //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. - //(Terrible reference to ) - //This format may turn out to be too unwieldy to keep... - //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? - //Sequential numbers, right? Ugh. Almost makes TOO much sense. - private string GetTimeString () - { - StringWriter sw = new StringWriter (); - sw.Write ("_"); - DateTime now = DateTime.Now; - sw.Write (now.Year); - sw.Write ("y_"); - sw.Write (now.Month); - sw.Write ("M_"); - sw.Write (now.Day); - sw.Write ("d_"); - sw.Write (now.Hour); - sw.Write ("h_"); - sw.Write (now.Minute); - sw.Write ("m_"); - sw.Write (now.Second); - sw.Write ("s"); - sw.Flush (); - string output = sw.ToString (); - sw.Close (); - return output; - } - - //Get the next logical file name - //I really shouldn't put fields here, but for now.... ;) - private string m_dirName = null; - private string m_regionName = null; - private string GetNextFile (string dirName, string regionName) - { - FileInfo uniqueFile = null; - m_dirName = dirName; - m_regionName = regionName; - long biggestExistingFile = HalfIntervalMaximize (1, FileExistsTest); - biggestExistingFile++; - //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. - uniqueFile = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); - if (uniqueFile.Exists) { - //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! - //Now you get to pay the performance cost :) - uniqueFile = UniqueFileSearchLinear (biggestExistingFile); - } - - return uniqueFile.FullName; - } - - /* - * Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. - * */ - private bool RunHeuristics (IScene region) - { - try { - return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); - } catch (Exception e) { - m_log.Warn ("[AUTO BACKUP]: Exception in RunHeuristics", e); - return false; - } - } - - /* - * If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), - * then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). - * AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". - * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - * */ - private bool RunTimeDilationHeuristic (IScene region) - { - string regionName = region.RegionInfo.RegionName; - return region.TimeDilation >= m_configSource.Configs["AutoBackupModule"].GetFloat (regionName + ".AutoBackupDilationThreshold", 0.5f); - } - - /* - * If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), - * then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). - * AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". - * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - * */ - private bool RunAgentLimitHeuristic (IScene region) - { - string regionName = region.RegionInfo.RegionName; - try { - Scene scene = (Scene)region; - //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... - return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); - } catch (InvalidCastException ice) { - m_log.Debug ("[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); - return true; - //Non-obstructionist safest answer... - } - } - - private void ExecuteScript (string scriptName, string savePath) - { - //Fast path out - if (scriptName == null || scriptName.Length <= 0) - return; - - try { - FileInfo fi = new FileInfo (scriptName); - if (fi.Exists) { - ProcessStartInfo psi = new ProcessStartInfo (scriptName); - psi.Arguments = savePath; - psi.CreateNoWindow = true; - Process proc = Process.Start (psi); - proc.ErrorDataReceived += HandleProcErrorDataReceived; - } - } catch (Exception e) { - m_log.Warn ("Exception encountered when trying to run script for oar backup " + savePath, e); - } - } - - void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) - { - m_log.Warn ("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); - } - - private void StopAllTimers () - { - foreach (Timer t in timerMap.Keys) { - t.Close (); - } - m_closed = true; - } - - /* Find the largest value for which the predicate returns true. - * We use a bisection algorithm (half interval) to make the algorithm scalable. - * The worst-case complexity is about O(log(n)^2) in practice. - * Only for extremely small values (under 10) do you notice it taking more iterations than a linear search. - * The number of predicate invocations only hits a few hundred when the maximized value - * is in the tens of millions, so prepare for the predicate to be invoked between 10 and 100 times. - * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. - * The Predicate parameter must be a function that accepts a long and returns a bool. - * */ - public long HalfIntervalMaximize (long start, Predicate pred) - { - long prev = start, curr = start, biggest = 0; - - if (start < 0) - throw new IndexOutOfRangeException ("Start value for HalfIntervalMaximize must be non-negative"); - - do { - if (pred (curr)) { - if (curr > biggest) { - biggest = curr; - } - prev = curr; - if (curr == 0) { - //Special case because 0 * 2 = 0 :) - curr = 1; - } else { - //Look deeper - curr *= 2; - } - } else { - // We went too far, back off halfway - curr = (curr + prev) / 2; - } - } while (curr - prev > 0); - - return biggest; - } - - public bool FileExistsTest (long num) - { - FileInfo test = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); - return test.Exists; - } - - - //Very slow, hence why we try the HalfIntervalMaximize first! - public FileInfo UniqueFileSearchLinear (long start) - { - long l = start; - FileInfo retval = null; - do { - retval = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); - } while (retval.Exists); - - return retval; - } - } - -} - +#pragma warning disable 1587 +/// +/// 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 OpenSimulator 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.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Timers; +using System.Text.RegularExpressions; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +/// +/// Config Settings Documentation. +/// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: +/// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. +/// IMPORTANT: You may optionally specify the key name as follows for a per-region key: . +/// Example: My region is named Foo. +/// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. +/// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. +/// Region-specific settings take precedence. +/// +/// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. +/// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! +/// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. +/// This is the only required option for enabling auto-backup; the other options have sane defaults. +/// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. +/// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. +/// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). +/// The number of minutes between each backup attempt. +/// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. +/// AutoBackupBusyCheck: True/False. Default: True. +/// If True, we will only take an auto-backup if a set of conditions are met. +/// These conditions are heuristics to try and avoid taking a backup when the sim is busy. +/// AutoBackupScript: String. Default: not specified (disabled). +/// File path to an executable script or binary to run when an automatic backup is taken. +/// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. +/// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! +/// argv[1] of the executed file/script will be the file name of the generated OAR. +/// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. +/// AutoBackupNaming: string. Default: Time. +/// One of three strings (case insensitive): +/// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. +/// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. +/// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. +/// AutoBackupDir: String. Default: "." (the current directory). +/// A directory (absolute or relative) where backups should be saved. +/// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. +/// If the time dilation is below this value, don't take a backup right now. +/// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. +/// If the number of agents is greater than this value, don't take a backup right now. +/// + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + public enum NamingType + { + Time, + Sequential, + Overwrite + } + + public class AutoBackupModule : ISharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. + /// Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. + private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); + + /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. + /// Also helps if you don't want AutoBackup at all + private readonly Dictionary m_states = + new Dictionary(1); + + private readonly Dictionary> m_timerMap = + new Dictionary>(1); + + private readonly Dictionary m_timers = new Dictionary(1); + + private bool m_enabled; + + /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! + private bool m_closed; + + private IConfigSource m_configSource; + + public bool IsSharedModule + { + get { return true; } + } + + #region ISharedRegionModule Members + + string IRegionModuleBase.Name + { + get { return "AutoBackupModule"; } + } + + Type IRegionModuleBase.ReplaceableInterface + { + get { return null; } + } + + void IRegionModuleBase.Initialise(IConfigSource source) + { + /// Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + this.m_configSource = source; + IConfig moduleConfig = source.Configs["AutoBackupModule"]; + if (moduleConfig == null) + { + this.m_enabled = false; + return; + } + else + { + this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); + if (this.m_enabled) + { + m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); + } + else + { + return; + } + } + + Timer defTimer = new Timer(43200000); + this.m_defaultState.Timer = defTimer; + this.m_timers.Add(43200000, defTimer); + defTimer.Elapsed += this.HandleElapsed; + defTimer.AutoReset = true; + defTimer.Start(); + + AutoBackupModuleState abms = this.ParseConfig(null, true); + m_log.Debug("[AUTO BACKUP]: Here is the default config:"); + m_log.Debug(abms.ToString()); + } + + void IRegionModuleBase.Close() + { + if (!this.m_enabled) + { + return; + } + + /// We don't want any timers firing while the sim's coming down; strange things may happen. + this.StopAllTimers(); + } + + void IRegionModuleBase.AddRegion(Scene scene) + { + /// NO-OP. Wait for the region to be loaded. + } + + void IRegionModuleBase.RemoveRegion(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + if (this.m_states.ContainsKey(scene)) + { + AutoBackupModuleState abms = this.m_states[scene]; + + /// Remove this scene out of the timer map list + Timer timer = abms.Timer; + List list = this.m_timerMap[timer]; + list.Remove(scene); + + /// Shut down the timer if this was the last scene for the timer + if (list.Count == 0) + { + this.m_timerMap.Remove(timer); + this.m_timers.Remove(timer.Interval); + timer.Close(); + } + this.m_states.Remove(scene); + } + } + + void IRegionModuleBase.RegionLoaded(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + /// This really ought not to happen, but just in case, let's pretend it didn't... + if (scene == null) + { + return; + } + + AutoBackupModuleState abms = this.ParseConfig(scene, false); + m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); + m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); + } + + void ISharedRegionModule.PostInitialise() + { + /// I don't care right now. + } + + #endregion + + private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) + { + string sRegionName; + string sRegionLabel; + string prepend; + AutoBackupModuleState state; + + if (parseDefault) + { + sRegionName = null; + sRegionLabel = "DEFAULT"; + prepend = ""; + state = this.m_defaultState; + } + else + { + sRegionName = scene.RegionInfo.RegionName; + sRegionLabel = sRegionName; + prepend = sRegionName + "."; + state = null; + } + + /// Read the config settings and set variables. + IConfig config = this.m_configSource.Configs["AutoBackupModule"]; + if (config == null) + { + /// defaultState would be disabled too if the section doesn't exist. + state = this.m_defaultState; + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + + bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); + if (state == null && tmpEnabled != this.m_defaultState.Enabled) + //Varies from default state + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Enabled = tmpEnabled; + } + + /// If you don't want AutoBackup, we stop. + if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) + { + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + else + { + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); + } + + /// Borrow an existing timer if one exists for the same interval; otherwise, make a new one. + double interval = + config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* + 60000.0; + if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) + { + state = new AutoBackupModuleState(); + } + + if (this.m_timers.ContainsKey(interval)) + { + if (state != null) + { + state.Timer = this.m_timers[interval]; + } + m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + + sRegionLabel); + } + else + { + /// 0 or negative interval == do nothing. + if (interval <= 0.0 && state != null) + { + state.Enabled = false; + return state; + } + if (state == null) + { + state = new AutoBackupModuleState(); + } + Timer tim = new Timer(interval); + state.Timer = tim; + //Milliseconds -> minutes + this.m_timers.Add(interval, tim); + tim.Elapsed += this.HandleElapsed; + tim.AutoReset = true; + tim.Start(); + } + + /// Add the current region to the list of regions tied to this timer. + if (scene != null) + { + if (state != null) + { + if (this.m_timerMap.ContainsKey(state.Timer)) + { + this.m_timerMap[state.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(state.Timer, scns); + } + } + else + { + if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) + { + this.m_timerMap[this.m_defaultState.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(this.m_defaultState.Timer, scns); + } + } + } + + bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", + this.m_defaultState.BusyCheck); + if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BusyCheck = tmpBusyCheck; + } + + /// Set file naming algorithm + string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", + this.m_defaultState.NamingType.ToString()); + NamingType tmpNamingType; + if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Time; + } + else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Sequential; + } + else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Overwrite; + } + else + { + m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + + stmpNamingType); + tmpNamingType = NamingType.Time; + } + + if (state == null && tmpNamingType != this.m_defaultState.NamingType) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.NamingType = tmpNamingType; + } + + string tmpScript = config.GetString(prepend + "AutoBackupScript", + this.m_defaultState.Script); + if (state == null && tmpScript != this.m_defaultState.Script) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Script = tmpScript; + } + + string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); + if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BackupDir = tmpBackupDir; + /// Let's give the user *one* convenience and auto-mkdir + if (state.BackupDir != ".") + { + try + { + DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); + if (!dirinfo.Exists) + { + dirinfo.Create(); + } + } + catch (Exception e) + { + m_log.Warn( + "BAD NEWS. You won't be able to save backups to directory " + + state.BackupDir + + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", + e); + } + } + } + + return state; + } + + private void HandleElapsed(object sender, ElapsedEventArgs e) + { + /// TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region + /// XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to + /// check whether the region is too busy! Especially on sims with LOTS of regions. + /// Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, + /// but would allow us to be semantically correct while being easier on perf. + /// Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... + if (this.m_closed) + { + return; + } + bool heuristicsRun = false; + bool heuristicsPassed = false; + if (!this.m_timerMap.ContainsKey((Timer) sender)) + { + m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); + } + + List tmap = this.m_timerMap[(Timer) sender]; + if (tmap != null && tmap.Count > 0) + { + foreach (IScene scene in tmap) + { + AutoBackupModuleState state = this.m_states[scene]; + bool heuristics = state.BusyCheck; + + /// Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. + if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) + { + this.DoRegionBackup(scene); + /// Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + } + else if (heuristicsRun) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + /// Logical Deduction: heuristics are on but haven't been run + } + else + { + heuristicsPassed = this.RunHeuristics(scene); + heuristicsRun = true; + if (!heuristicsPassed) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + } + this.DoRegionBackup(scene); + } + } + } + } + + private void DoRegionBackup(IScene scene) + { + if (scene.RegionStatus != RegionStatus.Up) + { + /// We won't backup a region that isn't operating normally. + m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + + " because its status is " + scene.RegionStatus); + return; + } + + AutoBackupModuleState state = this.m_states[scene]; + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, + state.BackupDir, + state.NamingType); + /// m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); + if (savePath == null) + { + m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); + return; + } + iram.ArchiveRegion(savePath, Guid.NewGuid(), null); + ExecuteScript(state.Script, savePath); + } + + /// This format may turn out to be too unwieldy to keep... + /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? + /// Sequential numbers, right? Ugh. Almost makes TOO much sense. + private static string GetTimeString() + { + StringWriter sw = new StringWriter(); + sw.Write("_"); + DateTime now = DateTime.Now; + sw.Write(now.Year); + sw.Write("y_"); + sw.Write(now.Month); + sw.Write("M_"); + sw.Write(now.Day); + sw.Write("d_"); + sw.Write(now.Hour); + sw.Write("h_"); + sw.Write(now.Minute); + sw.Write("m_"); + sw.Write(now.Second); + sw.Write("s"); + sw.Flush(); + string output = sw.ToString(); + sw.Close(); + return output; + } + + /// + /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. + /// + private bool RunHeuristics(IScene region) + { + try + { + return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); + } + catch (Exception e) + { + m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); + return false; + } + } + + /// + /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), + /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). + /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". + /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + /// + private bool RunTimeDilationHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + return region.TimeDilation >= + this.m_configSource.Configs["AutoBackupModule"].GetFloat( + regionName + ".AutoBackupDilationThreshold", 0.5f); + } + + /// + /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), + /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). + /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". + /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + /// + private bool RunAgentLimitHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + try + { + Scene scene = (Scene) region; + /// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... + return scene.GetRootAgentCount() <= + this.m_configSource.Configs["AutoBackupModule"].GetInt( + regionName + ".AutoBackupAgentThreshold", 10); + } + catch (InvalidCastException ice) + { + m_log.Debug( + "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", + ice); + return true; + /// Non-obstructionist safest answer... + } + } + + private static void ExecuteScript(string scriptName, string savePath) + { + //Fast path out + if (scriptName == null || scriptName.Length <= 0) + { + return; + } + + try + { + FileInfo fi = new FileInfo(scriptName); + if (fi.Exists) + { + ProcessStartInfo psi = new ProcessStartInfo(scriptName); + psi.Arguments = savePath; + psi.CreateNoWindow = true; + Process proc = Process.Start(psi); + proc.ErrorDataReceived += HandleProcErrorDataReceived; + } + } + catch (Exception e) + { + m_log.Warn( + "Exception encountered when trying to run script for oar backup " + savePath, e); + } + } + + private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) + { + m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + + " is yacking on stderr: " + e.Data); + } + + private void StopAllTimers() + { + foreach (Timer t in this.m_timerMap.Keys) + { + t.Close(); + } + this.m_closed = true; + } + + private static string GetNextFile(string dirName, string regionName) + { + FileInfo uniqueFile = null; + long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); + biggestExistingFile++; + //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + uniqueFile = + new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + + biggestExistingFile + ".oar"); + return uniqueFile.FullName; + } + + private static string BuildOarPath(string regionName, string baseDir, NamingType naming) + { + FileInfo path = null; + switch (naming) + { + case NamingType.Overwrite: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); + return path.FullName; + case NamingType.Time: + path = + new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + + GetTimeString() + ".oar"); + return path.FullName; + case NamingType.Sequential: + /// All codepaths in GetNextFile should return a file name ending in .oar + path = new FileInfo(GetNextFile(baseDir, regionName)); + return path.FullName; + default: + m_log.Warn("VERY BAD: Unhandled case element " + naming); + break; + } + + return null; + } + + private static long GetNextOarFileNumber(string dirName, string regionName) + { + long retval = 1; + + DirectoryInfo di = new DirectoryInfo(dirName); + FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); + Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); + + if (fi.LongLength > 0) + { + long subtract = 1L; + bool worked = false; + Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); + + while (!worked && subtract <= fi.LongLength) + { + /// Pick the file with the last natural ordering + string biggestFileName = fi[fi.LongLength - subtract].Name; + MatchCollection matches = reg.Matches(biggestFileName); + long l = 1; + if (matches.Count > 0 && matches[0].Groups.Count > 0) + { + try + { + long.TryParse(matches[0].Groups[1].Value, out l); + retval = l; + worked = true; + } + catch (FormatException fe) + { + m_log.Warn( + "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", + fe); + subtract++; + } + } + else + { + subtract++; + } + } + } + return retval; + } + } +} + + diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs new file mode 100644 index 0000000000..1b348af5eb --- /dev/null +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -0,0 +1,109 @@ +#pragma warning disable 1587 +/// +/// 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 OpenSimulator 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. +/// + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + /// AutoBackupModuleState: Auto-Backup state for one region (scene). + public class AutoBackupModuleState + { + public AutoBackupModuleState() + { + this.Enabled = false; + this.BackupDir = "."; + this.BusyCheck = true; + this.Timer = null; + this.NamingType = NamingType.Time; + this.Script = null; + } + + public bool Enabled + { + get; + set; + } + + public System.Timers.Timer Timer + { + get; + set; + } + + public double IntervalMinutes + { + get + { + if (this.Timer == null) + { + return -1.0; + } + else + { + return this.Timer.Interval / 60000.0; + } + } + } + + public bool BusyCheck + { + get; + set; + } + + public string Script + { + get; + set; + } + + public string BackupDir + { + get; + set; + } + + public NamingType NamingType + { + get; + set; + } + + public new string ToString() + { + string retval = ""; + + retval += "[AUTO BACKUP]: AutoBackup: " + (Enabled ? "ENABLED" : "DISABLED") + "\n"; + retval += "[AUTO BACKUP]: Interval: " + IntervalMinutes + " minutes" + "\n"; + retval += "[AUTO BACKUP]: Do Busy Check: " + (BusyCheck ? "Yes" : "No") + "\n"; + retval += "[AUTO BACKUP]: Naming Type: " + NamingType.ToString() + "\n"; + retval += "[AUTO BACKUP]: Backup Dir: " + BackupDir + "\n"; + retval += "[AUTO BACKUP]: Script: " + Script + "\n"; + return retval; + } + } +} + diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index 96ffb7ea6b..db4bb6ae8f 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -1277,6 +1277,10 @@ [GridService] ;; default standalone, overridable in StandaloneCommon.ini StorageProvider = "OpenSim.Data.Null.dll:NullRegionData" + +[AutoBackupModule] + ;; default is module is disabled at the top level + AutoBackupModuleEnabled = false [Modules] Include-modules = "addon-modules/*/config/*.ini" From 440d54a52158057a93d8bd08c5e46290e69d8f3f Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 23 Apr 2011 18:32:08 -0400 Subject: [PATCH 23/78] AutoBackup: Removed unneeded imports. --- .../Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index a4dbea406a..7c0a5c6c57 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -29,7 +29,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Reflection; using System.Timers; From c82c7e6ed9ecf4858d5afffa635013bd14e90bdf Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Tue, 26 Apr 2011 11:42:06 -0400 Subject: [PATCH 24/78] Wait for OnOarFileSaved event callback before executing script We want to execute the (optional) user script after I/O is done on the oar. I wasn't aware that ArchiveRegion is asynchronous -- now I am. Should fully resolve comment 0018290 at http://opensimulator.org/mantis/view.php?id=5440 --- .../World/AutoBackup/AutoBackupModule.cs | 23 ++++++++++++------- .../World/AutoBackup/AutoBackupModuleState.cs | 14 +++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 7c0a5c6c57..bd4893c08e 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -94,18 +94,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - /// True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. - /// Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. - private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); - /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. /// Also helps if you don't want AutoBackup at all + private readonly Dictionary m_pendingSaves = new Dictionary(1); + private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); private readonly Dictionary m_states = new Dictionary(1); - private readonly Dictionary> m_timerMap = new Dictionary>(1); - private readonly Dictionary m_timers = new Dictionary(1); private bool m_enabled; @@ -528,8 +524,19 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); return; } - iram.ArchiveRegion(savePath, Guid.NewGuid(), null); - ExecuteScript(state.Script, savePath); + Guid guid = Guid.NewGuid(); + m_pendingSaves.Add(guid, scene); + state.LiveRequests.Add(guid, savePath); + ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); + iram.ArchiveRegion(savePath, guid, null); + } + + void EventManager_OnOarFileSaved(Guid guid, string message) + { + AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; + ExecuteScript(abms.Script, abms.LiveRequests[guid]); + m_pendingSaves.Remove(guid); + abms.LiveRequests.Remove(guid); } /// This format may turn out to be too unwieldy to keep... diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs index 1b348af5eb..7fecfa435a 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -26,11 +26,17 @@ /// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// +using System; +using System.Collections.Generic; + + namespace OpenSim.Region.OptionalModules.World.AutoBackup { /// AutoBackupModuleState: Auto-Backup state for one region (scene). public class AutoBackupModuleState { + private Dictionary m_liveRequests = null; + public AutoBackupModuleState() { this.Enabled = false; @@ -41,6 +47,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.Script = null; } + public Dictionary LiveRequests + { + get { + return this.m_liveRequests ?? + (this.m_liveRequests = new Dictionary(1)); + } + } + public bool Enabled { get; From 2aab033aaa41f2ebef96e725b76e0153a673b448 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 2 May 2011 02:20:50 -0400 Subject: [PATCH 25/78] First pass at fixing justincc's feedback v2 ( http://opensimulator.org/mantis/view.php?id=5440 ) Fixing everything here (I think) except the per-region config. That's next. --- .../World/AutoBackup/AutoBackupModule.cs | 1577 +++++++++-------- .../World/AutoBackup/AutoBackupModuleState.cs | 59 +- 2 files changed, 868 insertions(+), 768 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index bd4893c08e..4a9615d1bc 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -1,751 +1,848 @@ -#pragma warning disable 1587 -/// -/// 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 OpenSimulator 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.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Timers; -using System.Text.RegularExpressions; -using log4net; -using Nini.Config; -using OpenSim.Framework; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -/// -/// Config Settings Documentation. -/// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: -/// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. -/// IMPORTANT: You may optionally specify the key name as follows for a per-region key: . -/// Example: My region is named Foo. -/// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. -/// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. -/// Region-specific settings take precedence. -/// -/// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. -/// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! -/// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. -/// This is the only required option for enabling auto-backup; the other options have sane defaults. -/// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. -/// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. -/// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). -/// The number of minutes between each backup attempt. -/// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. -/// AutoBackupBusyCheck: True/False. Default: True. -/// If True, we will only take an auto-backup if a set of conditions are met. -/// These conditions are heuristics to try and avoid taking a backup when the sim is busy. -/// AutoBackupScript: String. Default: not specified (disabled). -/// File path to an executable script or binary to run when an automatic backup is taken. -/// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. -/// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! -/// argv[1] of the executed file/script will be the file name of the generated OAR. -/// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. -/// AutoBackupNaming: string. Default: Time. -/// One of three strings (case insensitive): -/// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. -/// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. -/// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. -/// AutoBackupDir: String. Default: "." (the current directory). -/// A directory (absolute or relative) where backups should be saved. -/// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. -/// If the time dilation is below this value, don't take a backup right now. -/// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. -/// If the number of agents is greater than this value, don't take a backup right now. -/// - -namespace OpenSim.Region.OptionalModules.World.AutoBackup -{ - public enum NamingType - { - Time, - Sequential, - Overwrite - } - - public class AutoBackupModule : ISharedRegionModule - { - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. - /// Also helps if you don't want AutoBackup at all - private readonly Dictionary m_pendingSaves = new Dictionary(1); - private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); - private readonly Dictionary m_states = - new Dictionary(1); - private readonly Dictionary> m_timerMap = - new Dictionary>(1); - private readonly Dictionary m_timers = new Dictionary(1); - - private bool m_enabled; - +/* + * 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 OpenSimulator 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Timers; +using System.Text.RegularExpressions; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + /// + /// Choose between ways of naming the backup files that are generated. + /// + /// Time: OARs are named by a timestamp. + /// Sequential: OARs are named by counting (Region_1.oar, Region_2.oar, etc.) + /// Overwrite: Only one file per region is created; it's overwritten each time a backup is made. + public enum NamingType + { + Time, + Sequential, + Overwrite + } + + /// + /// AutoBackupModule: save OAR region backups to disk periodically + /// + /// + /// Config Settings Documentation. + /// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: + /// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. + /// IMPORTANT: You may optionally specify the key name as follows for a per-region key: [Region Name].[Key Name] + /// Example: My region is named Foo. + /// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. + /// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. + /// Region-specific settings take precedence. + /// + /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. + /// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! + /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. + /// This is the only required option for enabling auto-backup; the other options have sane defaults. + /// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. + /// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. + /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). + /// The number of minutes between each backup attempt. + /// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. + /// AutoBackupBusyCheck: True/False. Default: True. + /// If True, we will only take an auto-backup if a set of conditions are met. + /// These conditions are heuristics to try and avoid taking a backup when the sim is busy. + /// AutoBackupScript: String. Default: not specified (disabled). + /// File path to an executable script or binary to run when an automatic backup is taken. + /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. + /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! + /// argv[1] of the executed file/script will be the file name of the generated OAR. + /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. + /// AutoBackupNaming: string. Default: Time. + /// One of three strings (case insensitive): + /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. + /// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. + /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. + /// AutoBackupDir: String. Default: "." (the current directory). + /// A directory (absolute or relative) where backups should be saved. + /// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. + /// If the time dilation is below this value, don't take a backup right now. + /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. + /// If the number of agents is greater than this value, don't take a backup right now + /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. + /// Also helps if you don't want AutoBackup at all. + /// + public class AutoBackupModule : ISharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly Dictionary m_pendingSaves = new Dictionary(1); + private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); + private readonly Dictionary m_states = + new Dictionary(1); + private readonly Dictionary> m_timerMap = + new Dictionary>(1); + private readonly Dictionary m_timers = new Dictionary(1); + + private bool m_enabled; + + /// /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! - private bool m_closed; - - private IConfigSource m_configSource; - - public bool IsSharedModule + /// + private bool m_closed; + + private IConfigSource m_configSource; + + /// + /// Required by framework. + /// + public bool IsSharedModule + { + get { return true; } + } + + #region ISharedRegionModule Members + + /// + /// Identifies the module to the system. + /// + string IRegionModuleBase.Name + { + get { return "AutoBackupModule"; } + } + + /// + /// We don't implement an interface, this is a single-use module. + /// + Type IRegionModuleBase.ReplaceableInterface + { + get { return null; } + } + + /// + /// Called once in the lifetime of the module at startup. + /// + /// The input config source for OpenSim.ini. + void IRegionModuleBase.Initialise(IConfigSource source) + { + // Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + this.m_configSource = source; + IConfig moduleConfig = source.Configs["AutoBackupModule"]; + if (moduleConfig == null) + { + this.m_enabled = false; + return; + } + else + { + this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); + if (this.m_enabled) + { + m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); + } + else + { + return; + } + } + + Timer defTimer = new Timer(43200000); + this.m_defaultState.Timer = defTimer; + this.m_timers.Add(43200000, defTimer); + defTimer.Elapsed += this.HandleElapsed; + defTimer.AutoReset = true; + defTimer.Start(); + + AutoBackupModuleState abms = this.ParseConfig(null, true); + m_log.Debug("[AUTO BACKUP]: Here is the default config:"); + m_log.Debug(abms.ToString()); + } + + /// + /// Called once at de-init (sim shutting down). + /// + void IRegionModuleBase.Close() + { + if (!this.m_enabled) + { + return; + } + + // We don't want any timers firing while the sim's coming down; strange things may happen. + this.StopAllTimers(); + } + + /// + /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. + /// + /// + void IRegionModuleBase.AddRegion(Scene scene) + { + } + + /// + /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene. + /// + /// The scene (region) to stop performing AutoBackup on. + void IRegionModuleBase.RemoveRegion(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + if (this.m_states.ContainsKey(scene)) + { + AutoBackupModuleState abms = this.m_states[scene]; + + // Remove this scene out of the timer map list + Timer timer = abms.Timer; + List list = this.m_timerMap[timer]; + list.Remove(scene); + + // Shut down the timer if this was the last scene for the timer + if (list.Count == 0) + { + this.m_timerMap.Remove(timer); + this.m_timers.Remove(timer.Interval); + timer.Close(); + } + this.m_states.Remove(scene); + } + } + + /// + /// Most interesting/complex code paths in AutoBackup begin here. + /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc. + /// + /// The scene to (possibly) perform AutoBackup on. + void IRegionModuleBase.RegionLoaded(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + // This really ought not to happen, but just in case, let's pretend it didn't... + if (scene == null) + { + return; + } + + AutoBackupModuleState abms = this.ParseConfig(scene, false); + m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); + m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); + } + + /// + /// Currently a no-op. + /// + void ISharedRegionModule.PostInitialise() + { + } + + #endregion + + /// + /// Set up internal state for a given scene. Fairly complex code. + /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. + /// + /// The scene to look at. + /// Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings). + /// An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region. + private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) + { + string sRegionName; + string sRegionLabel; + string prepend; + AutoBackupModuleState state; + + if (parseDefault) + { + sRegionName = null; + sRegionLabel = "DEFAULT"; + prepend = ""; + state = this.m_defaultState; + } + else + { + sRegionName = scene.RegionInfo.RegionName; + sRegionLabel = sRegionName; + prepend = sRegionName + "."; + state = null; + } + + // Read the config settings and set variables. + IConfig config = this.m_configSource.Configs["AutoBackupModule"]; + if (config == null) + { + // defaultState would be disabled too if the section doesn't exist. + state = this.m_defaultState; + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + + bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); + if (state == null && tmpEnabled != this.m_defaultState.Enabled) + //Varies from default state + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Enabled = tmpEnabled; + } + + // If you don't want AutoBackup, we stop. + if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) + { + return state; + } + else + { + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); + } + + // Borrow an existing timer if one exists for the same interval; otherwise, make a new one. + double interval = + config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* + 60000.0; + if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) + { + state = new AutoBackupModuleState(); + } + + if (this.m_timers.ContainsKey(interval)) + { + if (state != null) + { + state.Timer = this.m_timers[interval]; + } + m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + + sRegionLabel); + } + else + { + // 0 or negative interval == do nothing. + if (interval <= 0.0 && state != null) + { + state.Enabled = false; + return state; + } + if (state == null) + { + state = new AutoBackupModuleState(); + } + Timer tim = new Timer(interval); + state.Timer = tim; + //Milliseconds -> minutes + this.m_timers.Add(interval, tim); + tim.Elapsed += this.HandleElapsed; + tim.AutoReset = true; + tim.Start(); + } + + // Add the current region to the list of regions tied to this timer. + if (scene != null) + { + if (state != null) + { + if (this.m_timerMap.ContainsKey(state.Timer)) + { + this.m_timerMap[state.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(state.Timer, scns); + } + } + else + { + if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) + { + this.m_timerMap[this.m_defaultState.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(this.m_defaultState.Timer, scns); + } + } + } + + bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", + this.m_defaultState.BusyCheck); + if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BusyCheck = tmpBusyCheck; + } + + // Set file naming algorithm + string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", + this.m_defaultState.NamingType.ToString()); + NamingType tmpNamingType; + if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Time; + } + else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Sequential; + } + else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Overwrite; + } + else + { + m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + + stmpNamingType); + tmpNamingType = NamingType.Time; + } + + if (state == null && tmpNamingType != this.m_defaultState.NamingType) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.NamingType = tmpNamingType; + } + + string tmpScript = config.GetString(prepend + "AutoBackupScript", + this.m_defaultState.Script); + if (state == null && tmpScript != this.m_defaultState.Script) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Script = tmpScript; + } + + string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); + if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BackupDir = tmpBackupDir; + // Let's give the user some convenience and auto-mkdir + if (state.BackupDir != ".") + { + try + { + DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); + if (!dirinfo.Exists) + { + dirinfo.Create(); + } + } + catch (Exception e) + { + m_log.Warn( + "BAD NEWS. You won't be able to save backups to directory " + + state.BackupDir + + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", + e); + } + } + } + + return state; + } + + /// + /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. + /// + /// + /// + private void HandleElapsed(object sender, ElapsedEventArgs e) + { + // TODO: heuristic thresholds are per-region, so we should probably run heuristics once per region + // XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to + // check whether the region is too busy! Especially on sims with LOTS of regions. + // Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, + // but would allow us to be semantically correct while being easier on perf. + // Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... + // Alternative 3: Don't support per-region heuristics at all; just accept them as a global only parameter. + // Since this is pretty experimental, I haven't decided which alternative makes the most sense. + if (this.m_closed) + { + return; + } + bool heuristicsRun = false; + bool heuristicsPassed = false; + if (!this.m_timerMap.ContainsKey((Timer) sender)) + { + m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); + } + + List tmap = this.m_timerMap[(Timer) sender]; + if (tmap != null && tmap.Count > 0) + { + foreach (IScene scene in tmap) + { + AutoBackupModuleState state = this.m_states[scene]; + bool heuristics = state.BusyCheck; + + // Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. + if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) + { + this.DoRegionBackup(scene); + // Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + } + else if (heuristicsRun) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + // Logical Deduction: heuristics are on but haven't been run + } + else + { + heuristicsPassed = this.RunHeuristics(scene); + heuristicsRun = true; + if (!heuristicsPassed) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + } + this.DoRegionBackup(scene); + } + } + } + } + + /// + /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable). + /// + /// + private void DoRegionBackup(IScene scene) + { + if (scene.RegionStatus != RegionStatus.Up) + { + // We won't backup a region that isn't operating normally. + m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + + " because its status is " + scene.RegionStatus); + return; + } + + AutoBackupModuleState state = this.m_states[scene]; + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, + state.BackupDir, + state.NamingType); + if (savePath == null) + { + m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); + return; + } + Guid guid = Guid.NewGuid(); + m_pendingSaves.Add(guid, scene); + state.LiveRequests.Add(guid, savePath); + ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); + iram.ArchiveRegion(savePath, guid, null); + } + + /// + /// Called by the Event Manager when the OnOarFileSaved event is fired. + /// + /// + /// + void EventManager_OnOarFileSaved(Guid guid, string message) { - get { return true; } - } - - #region ISharedRegionModule Members - - string IRegionModuleBase.Name - { - get { return "AutoBackupModule"; } - } - - Type IRegionModuleBase.ReplaceableInterface - { - get { return null; } - } - - void IRegionModuleBase.Initialise(IConfigSource source) - { - /// Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module - this.m_configSource = source; - IConfig moduleConfig = source.Configs["AutoBackupModule"]; - if (moduleConfig == null) + // Ignore if the OAR save is being done by some other part of the system + if (m_pendingSaves.ContainsKey(guid)) { - this.m_enabled = false; - return; + AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; + ExecuteScript(abms.Script, abms.LiveRequests[guid]); + m_pendingSaves.Remove(guid); + abms.LiveRequests.Remove(guid); } - else - { - this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); - if (this.m_enabled) - { - m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); - } - else - { - return; - } - } - - Timer defTimer = new Timer(43200000); - this.m_defaultState.Timer = defTimer; - this.m_timers.Add(43200000, defTimer); - defTimer.Elapsed += this.HandleElapsed; - defTimer.AutoReset = true; - defTimer.Start(); - - AutoBackupModuleState abms = this.ParseConfig(null, true); - m_log.Debug("[AUTO BACKUP]: Here is the default config:"); - m_log.Debug(abms.ToString()); - } - - void IRegionModuleBase.Close() - { - if (!this.m_enabled) - { - return; - } - - /// We don't want any timers firing while the sim's coming down; strange things may happen. - this.StopAllTimers(); - } - - void IRegionModuleBase.AddRegion(Scene scene) - { - /// NO-OP. Wait for the region to be loaded. - } - - void IRegionModuleBase.RemoveRegion(Scene scene) - { - if (!this.m_enabled) - { - return; - } - - if (this.m_states.ContainsKey(scene)) - { - AutoBackupModuleState abms = this.m_states[scene]; - - /// Remove this scene out of the timer map list - Timer timer = abms.Timer; - List list = this.m_timerMap[timer]; - list.Remove(scene); - - /// Shut down the timer if this was the last scene for the timer - if (list.Count == 0) - { - this.m_timerMap.Remove(timer); - this.m_timers.Remove(timer.Interval); - timer.Close(); - } - this.m_states.Remove(scene); - } - } - - void IRegionModuleBase.RegionLoaded(Scene scene) - { - if (!this.m_enabled) - { - return; - } - - /// This really ought not to happen, but just in case, let's pretend it didn't... - if (scene == null) - { - return; - } - - AutoBackupModuleState abms = this.ParseConfig(scene, false); - m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); - m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); - } - - void ISharedRegionModule.PostInitialise() - { - /// I don't care right now. - } - - #endregion - - private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) - { - string sRegionName; - string sRegionLabel; - string prepend; - AutoBackupModuleState state; - - if (parseDefault) - { - sRegionName = null; - sRegionLabel = "DEFAULT"; - prepend = ""; - state = this.m_defaultState; - } - else - { - sRegionName = scene.RegionInfo.RegionName; - sRegionLabel = sRegionName; - prepend = sRegionName + "."; - state = null; - } - - /// Read the config settings and set variables. - IConfig config = this.m_configSource.Configs["AutoBackupModule"]; - if (config == null) - { - /// defaultState would be disabled too if the section doesn't exist. - state = this.m_defaultState; - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } - - bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); - if (state == null && tmpEnabled != this.m_defaultState.Enabled) - //Varies from default state - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.Enabled = tmpEnabled; - } - - /// If you don't want AutoBackup, we stop. - if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) - { - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } - else - { - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); - } - - /// Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = - config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* - 60000.0; - if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) - { - state = new AutoBackupModuleState(); - } - - if (this.m_timers.ContainsKey(interval)) - { - if (state != null) - { - state.Timer = this.m_timers[interval]; - } - m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + - sRegionLabel); - } - else - { - /// 0 or negative interval == do nothing. - if (interval <= 0.0 && state != null) - { - state.Enabled = false; - return state; - } - if (state == null) - { - state = new AutoBackupModuleState(); - } - Timer tim = new Timer(interval); - state.Timer = tim; - //Milliseconds -> minutes - this.m_timers.Add(interval, tim); - tim.Elapsed += this.HandleElapsed; - tim.AutoReset = true; - tim.Start(); - } - - /// Add the current region to the list of regions tied to this timer. - if (scene != null) - { - if (state != null) - { - if (this.m_timerMap.ContainsKey(state.Timer)) - { - this.m_timerMap[state.Timer].Add(scene); - } - else - { - List scns = new List(1); - scns.Add(scene); - this.m_timerMap.Add(state.Timer, scns); - } - } - else - { - if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) - { - this.m_timerMap[this.m_defaultState.Timer].Add(scene); - } - else - { - List scns = new List(1); - scns.Add(scene); - this.m_timerMap.Add(this.m_defaultState.Timer, scns); - } - } - } - - bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", - this.m_defaultState.BusyCheck); - if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.BusyCheck = tmpBusyCheck; - } - - /// Set file naming algorithm - string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", - this.m_defaultState.NamingType.ToString()); - NamingType tmpNamingType; - if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) - { - tmpNamingType = NamingType.Time; - } - else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) - { - tmpNamingType = NamingType.Sequential; - } - else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) - { - tmpNamingType = NamingType.Overwrite; - } - else - { - m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + - stmpNamingType); - tmpNamingType = NamingType.Time; - } - - if (state == null && tmpNamingType != this.m_defaultState.NamingType) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.NamingType = tmpNamingType; - } - - string tmpScript = config.GetString(prepend + "AutoBackupScript", - this.m_defaultState.Script); - if (state == null && tmpScript != this.m_defaultState.Script) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.Script = tmpScript; - } - - string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); - if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.BackupDir = tmpBackupDir; - /// Let's give the user *one* convenience and auto-mkdir - if (state.BackupDir != ".") - { - try - { - DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); - if (!dirinfo.Exists) - { - dirinfo.Create(); - } - } - catch (Exception e) - { - m_log.Warn( - "BAD NEWS. You won't be able to save backups to directory " + - state.BackupDir + - " because it doesn't exist or there's a permissions issue with it. Here's the exception.", - e); - } - } - } - - return state; - } - - private void HandleElapsed(object sender, ElapsedEventArgs e) - { - /// TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region - /// XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to - /// check whether the region is too busy! Especially on sims with LOTS of regions. - /// Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, - /// but would allow us to be semantically correct while being easier on perf. - /// Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... - if (this.m_closed) - { - return; - } - bool heuristicsRun = false; - bool heuristicsPassed = false; - if (!this.m_timerMap.ContainsKey((Timer) sender)) - { - m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); - } - - List tmap = this.m_timerMap[(Timer) sender]; - if (tmap != null && tmap.Count > 0) - { - foreach (IScene scene in tmap) - { - AutoBackupModuleState state = this.m_states[scene]; - bool heuristics = state.BusyCheck; - - /// Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. - if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) - { - this.DoRegionBackup(scene); - /// Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! - } - else if (heuristicsRun) - { - m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + - scene.RegionInfo.RegionName + " right now."); - continue; - /// Logical Deduction: heuristics are on but haven't been run - } - else - { - heuristicsPassed = this.RunHeuristics(scene); - heuristicsRun = true; - if (!heuristicsPassed) - { - m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + - scene.RegionInfo.RegionName + " right now."); - continue; - } - this.DoRegionBackup(scene); - } - } - } - } - - private void DoRegionBackup(IScene scene) - { - if (scene.RegionStatus != RegionStatus.Up) - { - /// We won't backup a region that isn't operating normally. - m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + - " because its status is " + scene.RegionStatus); - return; - } - - AutoBackupModuleState state = this.m_states[scene]; - IRegionArchiverModule iram = scene.RequestModuleInterface(); - string savePath = BuildOarPath(scene.RegionInfo.RegionName, - state.BackupDir, - state.NamingType); - /// m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); - if (savePath == null) - { - m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); - return; - } - Guid guid = Guid.NewGuid(); - m_pendingSaves.Add(guid, scene); - state.LiveRequests.Add(guid, savePath); - ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); - iram.ArchiveRegion(savePath, guid, null); - } - - void EventManager_OnOarFileSaved(Guid guid, string message) - { - AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; - ExecuteScript(abms.Script, abms.LiveRequests[guid]); - m_pendingSaves.Remove(guid); - abms.LiveRequests.Remove(guid); - } - - /// This format may turn out to be too unwieldy to keep... + } + + /// This format may turn out to be too unwieldy to keep... /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? - /// Sequential numbers, right? Ugh. Almost makes TOO much sense. - private static string GetTimeString() - { - StringWriter sw = new StringWriter(); - sw.Write("_"); - DateTime now = DateTime.Now; - sw.Write(now.Year); - sw.Write("y_"); - sw.Write(now.Month); - sw.Write("M_"); - sw.Write(now.Day); - sw.Write("d_"); - sw.Write(now.Hour); - sw.Write("h_"); - sw.Write(now.Minute); - sw.Write("m_"); - sw.Write(now.Second); - sw.Write("s"); - sw.Flush(); - string output = sw.ToString(); - sw.Close(); - return output; - } - - /// - /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. - /// - private bool RunHeuristics(IScene region) - { - try - { - return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); - } - catch (Exception e) - { - m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); - return false; - } - } - - /// + /// Sequential numbers, right? We support those, too! + private static string GetTimeString() + { + StringWriter sw = new StringWriter(); + sw.Write("_"); + DateTime now = DateTime.Now; + sw.Write(now.Year); + sw.Write("y_"); + sw.Write(now.Month); + sw.Write("M_"); + sw.Write(now.Day); + sw.Write("d_"); + sw.Write(now.Hour); + sw.Write("h_"); + sw.Write(now.Minute); + sw.Write("m_"); + sw.Write(now.Second); + sw.Write("s"); + sw.Flush(); + string output = sw.ToString(); + sw.Close(); + return output; + } + + /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. + private bool RunHeuristics(IScene region) + { + try + { + return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); + } + catch (Exception e) + { + m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); + return false; + } + } + + /// /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". - /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - /// - private bool RunTimeDilationHeuristic(IScene region) - { - string regionName = region.RegionInfo.RegionName; - return region.TimeDilation >= - this.m_configSource.Configs["AutoBackupModule"].GetFloat( - regionName + ".AutoBackupDilationThreshold", 0.5f); - } - - /// + /// + /// + /// Returns true if we're not too busy; false means we've got worse time dilation than the threshold. + private bool RunTimeDilationHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + return region.TimeDilation >= + this.m_configSource.Configs["AutoBackupModule"].GetFloat( + regionName + ".AutoBackupDilationThreshold", 0.5f); + } + + /// /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". - /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - /// - private bool RunAgentLimitHeuristic(IScene region) - { - string regionName = region.RegionInfo.RegionName; - try - { - Scene scene = (Scene) region; - /// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... - return scene.GetRootAgentCount() <= - this.m_configSource.Configs["AutoBackupModule"].GetInt( - regionName + ".AutoBackupAgentThreshold", 10); - } - catch (InvalidCastException ice) - { - m_log.Debug( - "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", - ice); - return true; - /// Non-obstructionist safest answer... - } - } - - private static void ExecuteScript(string scriptName, string savePath) - { - //Fast path out - if (scriptName == null || scriptName.Length <= 0) - { - return; - } - - try - { - FileInfo fi = new FileInfo(scriptName); - if (fi.Exists) - { - ProcessStartInfo psi = new ProcessStartInfo(scriptName); - psi.Arguments = savePath; - psi.CreateNoWindow = true; - Process proc = Process.Start(psi); - proc.ErrorDataReceived += HandleProcErrorDataReceived; - } - } - catch (Exception e) - { - m_log.Warn( - "Exception encountered when trying to run script for oar backup " + savePath, e); - } - } - - private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) - { - m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + - " is yacking on stderr: " + e.Data); - } - - private void StopAllTimers() - { - foreach (Timer t in this.m_timerMap.Keys) - { - t.Close(); - } - this.m_closed = true; - } - - private static string GetNextFile(string dirName, string regionName) - { - FileInfo uniqueFile = null; - long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); - biggestExistingFile++; - //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. - uniqueFile = - new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + - biggestExistingFile + ".oar"); - return uniqueFile.FullName; - } - - private static string BuildOarPath(string regionName, string baseDir, NamingType naming) - { - FileInfo path = null; - switch (naming) - { - case NamingType.Overwrite: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); - return path.FullName; - case NamingType.Time: - path = - new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + - GetTimeString() + ".oar"); - return path.FullName; - case NamingType.Sequential: - /// All codepaths in GetNextFile should return a file name ending in .oar - path = new FileInfo(GetNextFile(baseDir, regionName)); - return path.FullName; - default: - m_log.Warn("VERY BAD: Unhandled case element " + naming); - break; - } - - return null; - } - - private static long GetNextOarFileNumber(string dirName, string regionName) - { - long retval = 1; - - DirectoryInfo di = new DirectoryInfo(dirName); - FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); - Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); - - if (fi.LongLength > 0) - { - long subtract = 1L; - bool worked = false; - Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); - - while (!worked && subtract <= fi.LongLength) - { - /// Pick the file with the last natural ordering - string biggestFileName = fi[fi.LongLength - subtract].Name; - MatchCollection matches = reg.Matches(biggestFileName); - long l = 1; - if (matches.Count > 0 && matches[0].Groups.Count > 0) - { - try - { - long.TryParse(matches[0].Groups[1].Value, out l); - retval = l; - worked = true; - } - catch (FormatException fe) - { - m_log.Warn( - "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", - fe); - subtract++; - } - } - else - { - subtract++; - } - } - } - return retval; - } - } -} - - + /// + /// + /// Returns true if we're not too busy; false means we've got more agents on the sim than the threshold. + private bool RunAgentLimitHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + try + { + Scene scene = (Scene) region; + // TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... + return scene.GetRootAgentCount() <= + this.m_configSource.Configs["AutoBackupModule"].GetInt( + regionName + ".AutoBackupAgentThreshold", 10); + } + catch (InvalidCastException ice) + { + m_log.Debug( + "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", + ice); + return true; + // Non-obstructionist safest answer... + } + } + + /// + /// Run the script or executable specified by the "AutoBackupScript" config setting. + /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. + /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions. + /// + /// + /// + private static void ExecuteScript(string scriptName, string savePath) + { + // Do nothing if there's no script. + if (scriptName == null || scriptName.Length <= 0) + { + return; + } + + try + { + FileInfo fi = new FileInfo(scriptName); + if (fi.Exists) + { + ProcessStartInfo psi = new ProcessStartInfo(scriptName); + psi.Arguments = savePath; + psi.CreateNoWindow = true; + Process proc = Process.Start(psi); + proc.ErrorDataReceived += HandleProcErrorDataReceived; + } + } + catch (Exception e) + { + m_log.Warn( + "Exception encountered when trying to run script for oar backup " + savePath, e); + } + } + + /// + /// Called if a running script process writes to stderr. + /// + /// + /// + private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) + { + m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + + " is yacking on stderr: " + e.Data); + } + + /// + /// Quickly stop all timers from firing. + /// + private void StopAllTimers() + { + foreach (Timer t in this.m_timerMap.Keys) + { + t.Close(); + } + this.m_closed = true; + } + + /// + /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. + /// + /// + /// + /// + private static string GetNextFile(string dirName, string regionName) + { + FileInfo uniqueFile = null; + long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); + biggestExistingFile++; + // We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + uniqueFile = + new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + + biggestExistingFile + ".oar"); + return uniqueFile.FullName; + } + + /// + /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants. + /// + /// Name of the region to save. + /// Absolute or relative path to the directory where the file should reside. + /// The naming scheme for the file name. + /// + private static string BuildOarPath(string regionName, string baseDir, NamingType naming) + { + FileInfo path = null; + switch (naming) + { + case NamingType.Overwrite: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); + return path.FullName; + case NamingType.Time: + path = + new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + + GetTimeString() + ".oar"); + return path.FullName; + case NamingType.Sequential: + // All codepaths in GetNextFile should return a file name ending in .oar + path = new FileInfo(GetNextFile(baseDir, regionName)); + return path.FullName; + default: + m_log.Warn("VERY BAD: Unhandled case element " + naming); + break; + } + + return null; + } + + /// + /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile). + /// + /// + /// + /// + private static long GetNextOarFileNumber(string dirName, string regionName) + { + long retval = 1; + + DirectoryInfo di = new DirectoryInfo(dirName); + FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); + Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); + + if (fi.LongLength > 0) + { + long subtract = 1L; + bool worked = false; + Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); + + while (!worked && subtract <= fi.LongLength) + { + // Pick the file with the last natural ordering + string biggestFileName = fi[fi.LongLength - subtract].Name; + MatchCollection matches = reg.Matches(biggestFileName); + long l = 1; + if (matches.Count > 0 && matches[0].Groups.Count > 0) + { + try + { + long.TryParse(matches[0].Groups[1].Value, out l); + retval = l; + worked = true; + } + catch (FormatException fe) + { + m_log.Warn( + "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", + fe); + subtract++; + } + } + else + { + subtract++; + } + } + } + return retval; + } + } +} + + diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs index 7fecfa435a..2db718c413 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -1,30 +1,29 @@ -#pragma warning disable 1587 -/// -/// 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 OpenSimulator 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. -/// +/* + * 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 OpenSimulator 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.Collections.Generic; @@ -32,7 +31,11 @@ using System.Collections.Generic; namespace OpenSim.Region.OptionalModules.World.AutoBackup { - /// AutoBackupModuleState: Auto-Backup state for one region (scene). + /// AutoBackupModuleState: Auto-Backup state for one region (scene). + /// If you use this class in any way outside of AutoBackupModule, you should treat the class as opaque. + /// Since it is not part of the framework, you really should not rely upon it outside of the AutoBackupModule implementation. + /// + /// public class AutoBackupModuleState { private Dictionary m_liveRequests = null; From 0995fedcaca9a921488929ee40f68c71fbba7a70 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 2 May 2011 04:32:31 -0400 Subject: [PATCH 26/78] AutoBackupModule: Implement per-region settings in Regions.ini. --- .../World/AutoBackup/AutoBackupModule.cs | 291 +++++++++++------- 1 file changed, 186 insertions(+), 105 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 4a9615d1bc..ce9a4481ea 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -55,7 +55,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup /// /// AutoBackupModule: save OAR region backups to disk periodically - /// + /// /// /// Config Settings Documentation. /// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: @@ -96,7 +96,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. /// If the number of agents is greater than this value, don't take a backup right now /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. - /// Also helps if you don't want AutoBackup at all. + /// Also helps if you don't want AutoBackup at all. /// public class AutoBackupModule : ISharedRegionModule { @@ -110,17 +110,18 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup new Dictionary>(1); private readonly Dictionary m_timers = new Dictionary(1); + private delegate T DefaultGetter(string settingName, T defaultValue); private bool m_enabled; - /// - /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! + /// + /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! /// private bool m_closed; private IConfigSource m_configSource; - /// - /// Required by framework. + /// + /// Required by framework. /// public bool IsSharedModule { @@ -129,25 +130,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #region ISharedRegionModule Members - /// - /// Identifies the module to the system. + /// + /// Identifies the module to the system. /// string IRegionModuleBase.Name { get { return "AutoBackupModule"; } } - /// - /// We don't implement an interface, this is a single-use module. + /// + /// We don't implement an interface, this is a single-use module. /// Type IRegionModuleBase.ReplaceableInterface { get { return null; } } - /// - /// Called once in the lifetime of the module at startup. - /// + /// + /// Called once in the lifetime of the module at startup. + /// /// The input config source for OpenSim.ini. void IRegionModuleBase.Initialise(IConfigSource source) { @@ -184,8 +185,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Debug(abms.ToString()); } - /// - /// Called once at de-init (sim shutting down). + /// + /// Called once at de-init (sim shutting down). /// void IRegionModuleBase.Close() { @@ -198,17 +199,17 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.StopAllTimers(); } - /// - /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. - /// + /// + /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. + /// /// void IRegionModuleBase.AddRegion(Scene scene) { } - /// - /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene. - /// + /// + /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene. + /// /// The scene (region) to stop performing AutoBackup on. void IRegionModuleBase.RemoveRegion(Scene scene) { @@ -237,10 +238,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Most interesting/complex code paths in AutoBackup begin here. - /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc. - /// + /// + /// Most interesting/complex code paths in AutoBackup begin here. + /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc. + /// /// The scene to (possibly) perform AutoBackup on. void IRegionModuleBase.RegionLoaded(Scene scene) { @@ -260,8 +261,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); } - /// - /// Currently a no-op. + /// + /// Currently a no-op. /// void ISharedRegionModule.PostInitialise() { @@ -269,12 +270,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #endregion - /// - /// Set up internal state for a given scene. Fairly complex code. - /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. - /// - /// The scene to look at. - /// Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings). + /// + /// Set up internal state for a given scene. Fairly complex code. + /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. + /// + /// The scene to look at. + /// Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings). /// An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region. private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) { @@ -299,16 +300,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } // Read the config settings and set variables. + IConfig regionConfig = (scene != null ? scene.Config.Configs[sRegionName] : null); IConfig config = this.m_configSource.Configs["AutoBackupModule"]; if (config == null) { // defaultState would be disabled too if the section doesn't exist. state = this.m_defaultState; - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); return state; } - bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); + bool tmpEnabled = ResolveBoolean("AutoBackup", this.m_defaultState.Enabled, config, regionConfig); if (state == null && tmpEnabled != this.m_defaultState.Enabled) //Varies from default state { @@ -332,8 +333,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup // Borrow an existing timer if one exists for the same interval; otherwise, make a new one. double interval = - config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* - 60000.0; + this.ResolveDouble("AutoBackupInterval", this.m_defaultState.IntervalMinutes, + config, regionConfig) * 60000.0; if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) { state = new AutoBackupModuleState(); @@ -400,8 +401,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", - this.m_defaultState.BusyCheck); + bool tmpBusyCheck = ResolveBoolean("AutoBackupBusyCheck", + this.m_defaultState.BusyCheck, config, regionConfig); if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) { state = new AutoBackupModuleState(); @@ -413,8 +414,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } // Set file naming algorithm - string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", - this.m_defaultState.NamingType.ToString()); + string stmpNamingType = ResolveString("AutoBackupNaming", + this.m_defaultState.NamingType.ToString(), config, regionConfig); NamingType tmpNamingType; if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) { @@ -445,8 +446,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.NamingType = tmpNamingType; } - string tmpScript = config.GetString(prepend + "AutoBackupScript", - this.m_defaultState.Script); + string tmpScript = ResolveString("AutoBackupScript", + this.m_defaultState.Script, config, regionConfig); if (state == null && tmpScript != this.m_defaultState.Script) { state = new AutoBackupModuleState(); @@ -457,7 +458,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.Script = tmpScript; } - string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); + string tmpBackupDir = ResolveString("AutoBackupDir", ".", config, regionConfig); if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) { state = new AutoBackupModuleState(); @@ -491,10 +492,90 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return state; } - /// - /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. - /// - /// + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private bool ResolveBoolean(string settingName, bool defaultValue, IConfig global, IConfig local) + { + if(local != null) + { + return local.GetBoolean(settingName, global.GetBoolean(settingName, defaultValue)); + } + else + { + return global.GetBoolean(settingName, defaultValue); + } + } + + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private double ResolveDouble(string settingName, double defaultValue, IConfig global, IConfig local) + { + if (local != null) + { + return local.GetDouble(settingName, global.GetDouble(settingName, defaultValue)); + } + else + { + return global.GetDouble(settingName, defaultValue); + } + } + + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private int ResolveInt(string settingName, int defaultValue, IConfig global, IConfig local) + { + if (local != null) + { + return local.GetInt(settingName, global.GetInt(settingName, defaultValue)); + } + else + { + return global.GetInt(settingName, defaultValue); + } + } + + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private string ResolveString(string settingName, string defaultValue, IConfig global, IConfig local) + { + if (local != null) + { + return local.GetString(settingName, global.GetString(settingName, defaultValue)); + } + else + { + return global.GetString(settingName, defaultValue); + } + } + + /// + /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. + /// + /// /// private void HandleElapsed(object sender, ElapsedEventArgs e) { @@ -554,9 +635,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable). - /// + /// + /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable). + /// /// private void DoRegionBackup(IScene scene) { @@ -585,25 +666,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup iram.ArchiveRegion(savePath, guid, null); } - /// - /// Called by the Event Manager when the OnOarFileSaved event is fired. - /// - /// + /// + /// Called by the Event Manager when the OnOarFileSaved event is fired. + /// + /// /// void EventManager_OnOarFileSaved(Guid guid, string message) - { - // Ignore if the OAR save is being done by some other part of the system - if (m_pendingSaves.ContainsKey(guid)) - { - AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; - ExecuteScript(abms.Script, abms.LiveRequests[guid]); - m_pendingSaves.Remove(guid); - abms.LiveRequests.Remove(guid); - } + { + // Ignore if the OAR save is being done by some other part of the system + if (m_pendingSaves.ContainsKey(guid)) + { + AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; + ExecuteScript(abms.Script, abms.LiveRequests[guid]); + m_pendingSaves.Remove(guid); + abms.LiveRequests.Remove(guid); + } } /// This format may turn out to be too unwieldy to keep... - /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? + /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? /// Sequential numbers, right? We support those, too! private static string GetTimeString() { @@ -642,12 +723,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), - /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). - /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". - /// - /// + /// + /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), + /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). + /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". + /// + /// /// Returns true if we're not too busy; false means we've got worse time dilation than the threshold. private bool RunTimeDilationHeuristic(IScene region) { @@ -657,12 +738,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup regionName + ".AutoBackupDilationThreshold", 0.5f); } - /// - /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), - /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). - /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". - /// - /// + /// + /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), + /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). + /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". + /// + /// /// Returns true if we're not too busy; false means we've got more agents on the sim than the threshold. private bool RunAgentLimitHeuristic(IScene region) { @@ -685,12 +766,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Run the script or executable specified by the "AutoBackupScript" config setting. - /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. - /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions. - /// - /// + /// + /// Run the script or executable specified by the "AutoBackupScript" config setting. + /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. + /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions. + /// + /// /// private static void ExecuteScript(string scriptName, string savePath) { @@ -719,10 +800,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Called if a running script process writes to stderr. - /// - /// + /// + /// Called if a running script process writes to stderr. + /// + /// /// private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) { @@ -730,8 +811,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup " is yacking on stderr: " + e.Data); } - /// - /// Quickly stop all timers from firing. + /// + /// Quickly stop all timers from firing. /// private void StopAllTimers() { @@ -742,11 +823,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.m_closed = true; } - /// - /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. - /// - /// - /// + /// + /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. + /// + /// + /// /// private static string GetNextFile(string dirName, string regionName) { @@ -760,12 +841,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return uniqueFile.FullName; } - /// - /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants. - /// - /// Name of the region to save. - /// Absolute or relative path to the directory where the file should reside. - /// The naming scheme for the file name. + /// + /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants. + /// + /// Name of the region to save. + /// Absolute or relative path to the directory where the file should reside. + /// The naming scheme for the file name. /// private static string BuildOarPath(string regionName, string baseDir, NamingType naming) { @@ -792,11 +873,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return null; } - /// - /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile). - /// - /// - /// + /// + /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile). + /// + /// + /// /// private static long GetNextOarFileNumber(string dirName, string regionName) { From bc49a0bc5df9e600af1e291fad3719949a592685 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 6 May 2011 00:09:08 +0100 Subject: [PATCH 27/78] Add "dump asset" command to the asset service for debugging purposes. This command dumps the asset with the given id to a file with the same name. --- OpenSim/Services/AssetService/AssetService.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/OpenSim/Services/AssetService/AssetService.cs b/OpenSim/Services/AssetService/AssetService.cs index e1f90b6e18..25a91f2762 100644 --- a/OpenSim/Services/AssetService/AssetService.cs +++ b/OpenSim/Services/AssetService/AssetService.cs @@ -26,9 +26,12 @@ */ using System; +using System.Collections.Generic; +using System.IO; using System.Reflection; using Nini.Config; using log4net; +using NDesk.Options; using OpenSim.Framework; using OpenSim.Framework.Console; using OpenSim.Data; @@ -60,6 +63,13 @@ namespace OpenSim.Services.AssetService "delete asset", "delete asset ", "Delete asset from database", HandleDeleteAsset); + + MainConsole.Instance.Commands.AddCommand("kfs", false, + "dump asset", + "dump asset ", + "Dump asset to a file", + "The filename is the same as the ID given.", + HandleDumpAsset); if (m_AssetLoader != null) { @@ -189,6 +199,39 @@ namespace OpenSim.Services.AssetService return false; } + + void HandleDumpAsset(string module, string[] args) + { + if (args.Length < 3) + { + MainConsole.Instance.Output("Usage is dump asset "); + return; + } + + string rawAssetId = args[2]; + UUID assetId; + + if (!UUID.TryParse(rawAssetId, out assetId)) + { + MainConsole.Instance.OutputFormat("ERROR: {0} is not a valid ID format", rawAssetId); + return; + } + + AssetBase asset = m_Database.GetAsset(assetId); + if (asset == null) + { + MainConsole.Instance.OutputFormat("ERROR: No asset found with ID {0}", assetId); + return; + } + + using (FileStream fs = new FileStream(rawAssetId, FileMode.CreateNew)) + { + using (BinaryWriter bw = new BinaryWriter(fs)) + { + bw.Write(asset.Data); + } + } + } void HandleShowDigest(string module, string[] args) { From 46baadbb65ac82260f46e580addda2f45efee120 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 6 May 2011 00:22:19 +0100 Subject: [PATCH 28/78] remove the NDesk.Options using since didn't end up needing it --- OpenSim/Services/AssetService/AssetService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenSim/Services/AssetService/AssetService.cs b/OpenSim/Services/AssetService/AssetService.cs index 25a91f2762..80d58e18e4 100644 --- a/OpenSim/Services/AssetService/AssetService.cs +++ b/OpenSim/Services/AssetService/AssetService.cs @@ -31,7 +31,6 @@ using System.IO; using System.Reflection; using Nini.Config; using log4net; -using NDesk.Options; using OpenSim.Framework; using OpenSim.Framework.Console; using OpenSim.Data; From 8755a48cde6ee77f421bef07e8b95cf8b68a76ed Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 6 May 2011 00:34:04 +0100 Subject: [PATCH 29/78] fix command display for debugging 'emergency-monitoring' --- .../OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index db17d8faf7..bdebbfbb8e 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -107,7 +107,7 @@ namespace OpenSim.Region.CoreModules.UDP.Linden scene.AddCommand( this, "emergency-monitoring", - "Go on/off emergency monitoring mode", + "emergency-monitoring", "Go on/off emergency monitoring mode", "Go on/off emergency monitoring mode", EmergencyMonitoring); From 8ca793875318efc8db3339b25bf7fa5ddeeac218 Mon Sep 17 00:00:00 2001 From: BlueWall Date: Sun, 1 May 2011 14:44:09 -0400 Subject: [PATCH 30/78] Adding ssl support Adding ssl support for "Out of Band" applications such as the remote admin module or Robust services --- OpenSim/Framework/MainServer.cs | 5 ++ OpenSim/Framework/NetworkServersInfo.cs | 15 ++++ .../Servers/HttpServer/BaseHttpServer.cs | 14 ++++ .../ClientStack/RegionApplicationBase.cs | 16 ++++ OpenSim/Server/Base/HttpServerBase.cs | 77 ++++++++++++++++++- bin/OpenSimDefaults.ini | 14 ++++ bin/Robust.ini.example | 21 +++++ 7 files changed, 160 insertions(+), 2 deletions(-) diff --git a/OpenSim/Framework/MainServer.cs b/OpenSim/Framework/MainServer.cs index 0515b166e2..a3e0a26bb7 100644 --- a/OpenSim/Framework/MainServer.cs +++ b/OpenSim/Framework/MainServer.cs @@ -52,6 +52,11 @@ namespace OpenSim.Framework return GetHttpServer(port,null); } + public static void AddHttpServer(BaseHttpServer server) + { + m_Servers.Add(server.Port, server); + } + public static IHttpServer GetHttpServer(uint port, IPAddress ipaddr) { if (port == 0) diff --git a/OpenSim/Framework/NetworkServersInfo.cs b/OpenSim/Framework/NetworkServersInfo.cs index b25f8b9f60..5bb4111c90 100644 --- a/OpenSim/Framework/NetworkServersInfo.cs +++ b/OpenSim/Framework/NetworkServersInfo.cs @@ -49,6 +49,12 @@ namespace OpenSim.Framework public string HttpSSLCN = ""; public uint httpSSLPort = 9001; + // "Out of band" managemnt https + public bool ssl_listener = false; + public uint https_port = 0; + public string cert_path = String.Empty; + public string cert_pass = String.Empty; + public string MessagingURL = String.Empty; public NetworkServersInfo() @@ -86,6 +92,15 @@ namespace OpenSim.Framework secureInventoryServer = config.Configs["Network"].GetBoolean("secure_inventory_server", true); MessagingURL = config.Configs["Network"].GetString("messaging_server_url", string.Empty); + + // "Out of band management https" + ssl_listener = config.Configs["Network"].GetBoolean("https_listener",false); + if( ssl_listener) + { + cert_path = config.Configs["Network"].GetString("cert_path",String.Empty); + cert_pass = config.Configs["Network"].GetString("cert_pass",String.Empty); + https_port = (uint)config.Configs["Network"].GetInt("https_port", 0); + } } } } diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index ba89e2117b..598e5d17cd 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs @@ -32,6 +32,7 @@ using System.Collections.Specialized; using System.IO; using System.Net; using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; using System.Reflection; using System.Globalization; using System.Text; @@ -72,6 +73,7 @@ namespace OpenSim.Framework.Servers.HttpServer protected uint m_port; protected uint m_sslport; protected bool m_ssl; + private X509Certificate2 m_cert; protected bool m_firstcaps = true; protected string m_SSLCommonName = ""; @@ -123,6 +125,14 @@ namespace OpenSim.Framework.Servers.HttpServer } } + public BaseHttpServer(uint port, bool ssl, string CPath, string CPass) : this (port, ssl) + { + if (m_ssl) + { + m_cert = new X509Certificate2(CPath, CPass); + } + } + /// /// Add a stream handler to the http server. If the handler already exists, then nothing happens. /// @@ -1683,6 +1693,7 @@ namespace OpenSim.Framework.Servers.HttpServer try { //m_httpListener = new HttpListener(); + NotSocketErrors = 0; if (!m_ssl) { @@ -1702,6 +1713,9 @@ namespace OpenSim.Framework.Servers.HttpServer { //m_httpListener.Prefixes.Add("https://+:" + (m_sslport) + "/"); //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); + m_httpListener2 = CoolHTTPListener.Create(IPAddress.Any, (int)m_port, m_cert); + m_httpListener2.ExceptionThrown += httpServerException; + m_httpListener2.LogWriter = httpserverlog; } m_httpListener2.RequestReceived += OnRequest; diff --git a/OpenSim/Region/ClientStack/RegionApplicationBase.cs b/OpenSim/Region/ClientStack/RegionApplicationBase.cs index ea1317a19c..6e3a58e63a 100644 --- a/OpenSim/Region/ClientStack/RegionApplicationBase.cs +++ b/OpenSim/Region/ClientStack/RegionApplicationBase.cs @@ -96,6 +96,22 @@ namespace OpenSim.Region.ClientStack MainServer.Instance = m_httpServer; + // "OOB" Server + if (m_networkServersInfo.ssl_listener) + { + BaseHttpServer server = null; + server = new BaseHttpServer( + m_networkServersInfo.https_port, m_networkServersInfo.ssl_listener, m_networkServersInfo.cert_path, + m_networkServersInfo.cert_pass); + // Add the server to m_Servers + if(server != null) + { + m_log.InfoFormat("[REGION SERVER]: Starting HTTPS server on port {0}", server.Port); + MainServer.AddHttpServer(server); + server.Start(); + } + } + base.StartupSpecific(); } diff --git a/OpenSim/Server/Base/HttpServerBase.cs b/OpenSim/Server/Base/HttpServerBase.cs index 9e4593e19e..bb5ce96757 100644 --- a/OpenSim/Server/Base/HttpServerBase.cs +++ b/OpenSim/Server/Base/HttpServerBase.cs @@ -97,16 +97,76 @@ namespace OpenSim.Server.Base if (port == 0) { - System.Console.WriteLine("Port number not specified or 0, server can't start"); + Thread.CurrentThread.Abort(); } + // + bool ssl_main = networkConfig.GetBoolean("https_main",false); + bool ssl_listener = networkConfig.GetBoolean("https_listener",false); m_consolePort = (uint)networkConfig.GetInt("ConsolePort", 0); m_Port = port; + // + // This is where to make the servers: + // + // + // Make the base server according to the port, etc. + // ADD: Possibility to make main server ssl + // Then, check for https settings and ADD a server to + // m_Servers + // + if ( !ssl_main ) + { + m_HttpServer = new BaseHttpServer(port); - m_HttpServer = new BaseHttpServer(port); + } + else + { + string cert_path = networkConfig.GetString("cert_path",String.Empty); + if ( cert_path == String.Empty ) + { + System.Console.WriteLine("Path to X509 certificate is missing, server can't start."); + Thread.CurrentThread.Abort(); + } + string cert_pass = networkConfig.GetString("cert_pass",String.Empty); + if ( cert_pass == String.Empty ) + { + System.Console.WriteLine("Password for X509 certificate is missing, server can't start."); + Thread.CurrentThread.Abort(); + } + m_HttpServer = new BaseHttpServer(port, ssl_main, cert_path, cert_pass); + } MainServer.Instance = m_HttpServer; + + // If https_listener = true, then add an ssl listener on the https_port... + if ( ssl_listener == true ) { + + uint https_port = (uint)networkConfig.GetInt("https_port", 0); + + string cert_path = networkConfig.GetString("cert_path",String.Empty); + if ( cert_path == String.Empty ) + { + System.Console.WriteLine("Path to X509 certificate is missing, server can't start."); + Thread.CurrentThread.Abort(); + } + string cert_pass = networkConfig.GetString("cert_pass",String.Empty); + if ( cert_pass == String.Empty ) + { + System.Console.WriteLine("Password for X509 certificate is missing, server can't start."); + Thread.CurrentThread.Abort(); + } + // Add our https_server + BaseHttpServer server = null; + server = new BaseHttpServer(https_port, ssl_listener, cert_path, cert_pass); + if (server != null) + { + m_Log.InfoFormat("[SERVER]: Starting HTTPS server on port {0}", https_port); + m_Servers.Add(https_port,server); + } + else + System.Console.WriteLine(String.Format("Failed to start HTTPS server on port {0}",https_port)); + } } protected override void Initialise() @@ -114,6 +174,19 @@ namespace OpenSim.Server.Base m_Log.InfoFormat("[SERVER]: Starting HTTP server on port {0}", m_HttpServer.Port); m_HttpServer.Start(); + if (m_Servers.Count > 0) + { + foreach (BaseHttpServer s in m_Servers.Values) + { + if (!s.UseSSL) + m_Log.InfoFormat("[SERVER]: Starting HTTP server on port {0}", s.Port); + else + m_Log.InfoFormat("[SERVER]: Starting HTTPS server on port {0}", s.Port); + + s.Start(); + } + } + if (MainConsole.Instance is RemoteConsole) { if (m_consolePort == 0) diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index 6d2d54daca..2e192f1178 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -291,6 +291,20 @@ http_listener_sslport = 9001 ; Use this port for SSL connections http_listener_ssl_cert = "" ; Currently unused, but will be used for OSHttpServer + ; HTTPS for "Out of band" management applications such as the remote + ; admin module + ; + ; Create https_listener = "True" will create a listener on the port + ; specified. Provide the path to your server certificate along with it's + ; password + ; https_listener = False + ; Set our listener to this port + ; https_port = 0 + ; Path to X509 certificate + ; cert_path = "path/to/cert.p12" + ; Password for cert + ; cert_pass = "password" + ; Hostname to use in llRequestURL/llRequestSecureURL ; if not defined - default machine name is being used ; (on Windows this mean NETBIOS name - useably only inside local network) diff --git a/bin/Robust.ini.example b/bin/Robust.ini.example index 047e9eeed6..cc018f8867 100644 --- a/bin/Robust.ini.example +++ b/bin/Robust.ini.example @@ -21,6 +21,27 @@ ServiceConnectors = "8003/OpenSim.Server.Handlers.dll:AssetServiceConnector,8003 [Network] port = 8003 + + ; HTTPS for "Out of band" management applications such as the remote admin + ; module. May specify https_main = True to make the main http server + ; use https or "False" to make the main server HTTP + ; https_main = False + ; + ; Create https_listener = "True" will create a listener on the port + ; specified. Provide the path to your server certificate along with it's + ; password + ; https_listener = False + ; + ; Set our listener to this port + ; https_port = 0 + ; + ; Path to X509 certificate + ; cert_path = "path/to/cert.p12" + ; + ; Password for cert + ; cert_pass = "password" + + ; * The following are for the remote console ; * They have no effect for the local or basic console types ; * Leave commented to diable logins to the console From e4e95706d52fad2bab4a725955449f6bdb523a29 Mon Sep 17 00:00:00 2001 From: BlueWall Date: Mon, 2 May 2011 14:35:44 -0400 Subject: [PATCH 31/78] Add support for llRequestSecureURL() if ssl is enabled --- .../Scripting/LSLHttp/UrlModule.cs | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs b/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs index 9b565ed85b..a552a28912 100644 --- a/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs @@ -78,7 +78,9 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp private int m_TotalUrls = 100; + private uint https_port = 0; private IHttpServer m_HttpServer = null; + private IHttpServer m_HttpsServer = null; private string m_ExternalHostNameForLSL = ""; @@ -100,6 +102,11 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp public void Initialise(IConfigSource config) { m_ExternalHostNameForLSL = config.Configs["Network"].GetString("ExternalHostNameForLSL", System.Environment.MachineName); + bool ssl_enabled = config.Configs["Network"].GetBoolean("https_listener",false); + if (ssl_enabled) + { + https_port = (uint) config.Configs["Network"].GetInt("https_port",0); + } } public void PostInitialise() @@ -113,6 +120,12 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp // There can only be one // m_HttpServer = MainServer.Instance; + // + // We can use the https if it is enabled + if (https_port > 0) + { + m_HttpsServer = MainServer.GetHttpServer(https_port); + } } scene.RegisterModuleInterface(this); @@ -171,7 +184,40 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp { UUID urlcode = UUID.Random(); - engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); + if (m_HttpsServer == null) + { + engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); + return urlcode; + } + + lock (m_UrlMap) + { + if (m_UrlMap.Count >= m_TotalUrls) + { + engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); + return urlcode; + } + string url = "https://" + m_ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + "/lslhttps/" + urlcode.ToString() + "/"; + + UrlData urlData = new UrlData(); + urlData.hostID = host.UUID; + urlData.itemID = itemID; + urlData.engine = engine; + urlData.url = url; + urlData.urlcode = urlcode; + urlData.requests = new Dictionary(); + + + m_UrlMap[url] = urlData; + + string uri = "/lslhttps/" + urlcode.ToString() + "/"; + + m_HttpsServer.AddPollServiceHTTPHandler(uri,HandleHttpPoll, + new PollServiceEventArgs(HttpRequestHandler,HasEvents, GetEvents, NoEvents, + urlcode)); + + engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_GRANTED", url }); + } return urlcode; } @@ -345,7 +391,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp } private Hashtable GetEvents(UUID requestID, UUID sessionID, string request) { - UrlData url = null; + UrlData url = null; RequestData requestData = null; lock (m_RequestMap) @@ -391,11 +437,12 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp lock (request) { string uri = request["uri"].ToString(); - + bool is_ssl = uri.Contains("lslhttps"); + try { Hashtable headers = (Hashtable)request["headers"]; - + // string uri_full = "http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri;// "/lslhttp/" + urlcode.ToString() + "/"; int pos1 = uri.IndexOf("/");// /lslhttp @@ -409,7 +456,11 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp pathInfo = uri.Substring(pos3); - UrlData url = m_UrlMap["http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri_tmp]; + UrlData url = null; + if (!is_ssl) + url = m_UrlMap["http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri_tmp]; + else + url = m_UrlMap["https://" + m_ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + uri_tmp]; //for llGetHttpHeader support we need to store original URI here //to make x-path-info / x-query-string / x-script-url / x-remote-ip headers From fd44540c023e7df35308a40df9e61d7f9273eba4 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 6 May 2011 01:06:28 +0100 Subject: [PATCH 32/78] add descriptive explanations for region restart functionality --- .../CoreModules/World/Region/RestartModule.cs | 19 +++++++++++++------ .../Agent/UDP/Linden/LindenUDPInfoModule.cs | 1 - 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Region/RestartModule.cs b/OpenSim/Region/CoreModules/World/Region/RestartModule.cs index ab6a598ace..e983239b4a 100644 --- a/OpenSim/Region/CoreModules/World/Region/RestartModule.cs +++ b/OpenSim/Region/CoreModules/World/Region/RestartModule.cs @@ -64,19 +64,26 @@ namespace OpenSim.Region.CoreModules.World.Region public void AddRegion(Scene scene) { m_Scene = scene; + scene.RegisterModuleInterface(this); MainConsole.Instance.Commands.AddCommand("RestartModule", false, "region restart bluebox", - "region restart bluebox public static OSDMap PutToService(string url, OSDMap data, int timeout) { - return ServiceOSDRequest(url,data, "PUT", timeout); + return ServiceOSDRequest(url,data, "PUT", timeout, false); } public static OSDMap PostToService(string url, OSDMap data, int timeout) { - return ServiceOSDRequest(url, data, "POST", timeout); + return ServiceOSDRequest(url, data, "POST", timeout, false); + } + + public static OSDMap PostToServiceCompressed(string url, OSDMap data, int timeout) + { + return ServiceOSDRequest(url, data, "POST", timeout, true); } public static OSDMap GetFromService(string url, int timeout) { - return ServiceOSDRequest(url, null, "GET", timeout); + return ServiceOSDRequest(url, null, "GET", timeout, false); } - public static OSDMap ServiceOSDRequest(string url, OSDMap data, string method, int timeout) + public static OSDMap ServiceOSDRequest(string url, OSDMap data, string method, int timeout, bool compressed) { int reqnum = m_requestNumber++; // m_log.DebugFormat("[WEB UTIL]: <{0}> start osd request for {1}, method {2}",reqnum,url,method); @@ -180,10 +186,31 @@ namespace OpenSim.Framework string strBuffer = OSDParser.SerializeJsonString(data); byte[] buffer = System.Text.Encoding.UTF8.GetBytes(strBuffer); - request.ContentType = "application/json"; - request.ContentLength = buffer.Length; //Count bytes to send - using (Stream requestStream = request.GetRequestStream()) - requestStream.Write(buffer, 0, buffer.Length); //Send it + if (compressed) + { + request.ContentType = "application/x-gzip"; + using (MemoryStream ms = new MemoryStream()) + { + using (GZipStream comp = new GZipStream(ms, CompressionMode.Compress)) + { + comp.Write(buffer, 0, buffer.Length); + comp.Flush(); + + ms.Seek(0, SeekOrigin.Begin); + + request.ContentLength = ms.Length; //Count bytes to send + using (Stream requestStream = request.GetRequestStream()) + requestStream.Write(ms.ToArray(), 0, (int)ms.Length); + } + } + } + else + { + request.ContentType = "application/json"; + request.ContentLength = buffer.Length; //Count bytes to send + using (Stream requestStream = request.GetRequestStream()) + requestStream.Write(buffer, 0, buffer.Length); //Send it + } } // capture how much time was spent writing, this may seem silly diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index e38006752a..73f07baf85 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -328,6 +328,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Let's create an agent there if one doesn't exist yet. bool logout = false; + sp.ControllingClient.SendTeleportProgress(teleportFlags | (uint)TeleportFlags.DisableCancel, "Creating agent..."); if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout)) { sp.ControllingClient.SendTeleportFailed(String.Format("Destination refused: {0}", diff --git a/OpenSim/Server/Handlers/Hypergrid/AgentHandlers.cs b/OpenSim/Server/Handlers/Hypergrid/AgentHandlers.cs index f3f81b07c9..cf1af15a37 100644 --- a/OpenSim/Server/Handlers/Hypergrid/AgentHandlers.cs +++ b/OpenSim/Server/Handlers/Hypergrid/AgentHandlers.cs @@ -49,13 +49,13 @@ using log4net; namespace OpenSim.Server.Handlers.Hypergrid { - public class GatekeeperAgentHandler : OpenSim.Server.Handlers.Simulation.AgentHandler + public class GatekeeperAgentHandler : OpenSim.Server.Handlers.Simulation.AgentPostHandler { // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private IGatekeeperService m_GatekeeperService; - public GatekeeperAgentHandler(IGatekeeperService gatekeeper, bool proxy) + public GatekeeperAgentHandler(IGatekeeperService gatekeeper, bool proxy) : base("/foreignagent") { m_GatekeeperService = gatekeeper; m_Proxy = proxy; @@ -65,7 +65,5 @@ namespace OpenSim.Server.Handlers.Hypergrid { return m_GatekeeperService.LoginAgent(aCircuit, destination, out reason); } - } - } diff --git a/OpenSim/Server/Handlers/Hypergrid/GatekeeperServerConnector.cs b/OpenSim/Server/Handlers/Hypergrid/GatekeeperServerConnector.cs index 3d0967f2de..0d4990a04f 100644 --- a/OpenSim/Server/Handlers/Hypergrid/GatekeeperServerConnector.cs +++ b/OpenSim/Server/Handlers/Hypergrid/GatekeeperServerConnector.cs @@ -73,7 +73,7 @@ namespace OpenSim.Server.Handlers.Hypergrid server.AddXmlRPCHandler("link_region", hghandlers.LinkRegionRequest, false); server.AddXmlRPCHandler("get_region", hghandlers.GetRegion, false); - server.AddHTTPHandler("/foreignagent/", new GatekeeperAgentHandler(m_GatekeeperService, m_Proxy).Handler); + server.AddStreamHandler(new GatekeeperAgentHandler(m_GatekeeperService, m_Proxy)); } public GatekeeperServiceInConnector(IConfigSource config, IHttpServer server) diff --git a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs index 8b6fb4f5c1..d52750b4e0 100644 --- a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs +++ b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs @@ -28,6 +28,7 @@ using System; using System.Collections; using System.IO; +using System.IO.Compression; using System.Reflection; using System.Net; using System.Text; @@ -53,8 +54,6 @@ namespace OpenSim.Server.Handlers.Simulation private ISimulationService m_SimulationService; - protected bool m_Proxy = false; - public AgentHandler() { } public AgentHandler(ISimulationService sim) @@ -91,16 +90,12 @@ namespace OpenSim.Server.Handlers.Simulation // Next, let's parse the verb string method = (string)request["http-method"]; + m_log.DebugFormat("[SIMULATION]: Got verb {0} in HTTP handler", method); if (method.Equals("PUT")) { DoAgentPut(request, responsedata); return responsedata; } - else if (method.Equals("POST")) - { - DoAgentPost(request, responsedata, agentID); - return responsedata; - } else if (method.Equals("GET")) { DoAgentGet(request, responsedata, agentID, regionID); @@ -127,111 +122,6 @@ namespace OpenSim.Server.Handlers.Simulation } - protected void DoAgentPost(Hashtable request, Hashtable responsedata, UUID id) - { - OSDMap args = Utils.GetOSDMap((string)request["body"]); - if (args == null) - { - responsedata["int_response_code"] = HttpStatusCode.BadRequest; - responsedata["str_response_string"] = "Bad request"; - return; - } - - // retrieve the input arguments - int x = 0, y = 0; - UUID uuid = UUID.Zero; - string regionname = string.Empty; - uint teleportFlags = 0; - if (args.ContainsKey("destination_x") && args["destination_x"] != null) - Int32.TryParse(args["destination_x"].AsString(), out x); - else - m_log.WarnFormat(" -- request didn't have destination_x"); - if (args.ContainsKey("destination_y") && args["destination_y"] != null) - Int32.TryParse(args["destination_y"].AsString(), out y); - else - m_log.WarnFormat(" -- request didn't have destination_y"); - if (args.ContainsKey("destination_uuid") && args["destination_uuid"] != null) - UUID.TryParse(args["destination_uuid"].AsString(), out uuid); - if (args.ContainsKey("destination_name") && args["destination_name"] != null) - regionname = args["destination_name"].ToString(); - if (args.ContainsKey("teleport_flags") && args["teleport_flags"] != null) - teleportFlags = args["teleport_flags"].AsUInteger(); - - GridRegion destination = new GridRegion(); - destination.RegionID = uuid; - destination.RegionLocX = x; - destination.RegionLocY = y; - destination.RegionName = regionname; - - AgentCircuitData aCircuit = new AgentCircuitData(); - try - { - aCircuit.UnpackAgentCircuitData(args); - } - catch (Exception ex) - { - m_log.InfoFormat("[AGENT HANDLER]: exception on unpacking ChildCreate message {0}", ex.Message); - responsedata["int_response_code"] = HttpStatusCode.BadRequest; - responsedata["str_response_string"] = "Bad request"; - return; - } - - OSDMap resp = new OSDMap(2); - string reason = String.Empty; - - // This is the meaning of POST agent - //m_regionClient.AdjustUserInformation(aCircuit); - //bool result = m_SimulationService.CreateAgent(destination, aCircuit, teleportFlags, out reason); - bool result = CreateAgent(destination, aCircuit, teleportFlags, out reason); - - resp["reason"] = OSD.FromString(reason); - resp["success"] = OSD.FromBoolean(result); - // Let's also send out the IP address of the caller back to the caller (HG 1.5) - resp["your_ip"] = OSD.FromString(GetCallerIP(request)); - - // TODO: add reason if not String.Empty? - responsedata["int_response_code"] = HttpStatusCode.OK; - responsedata["str_response_string"] = OSDParser.SerializeJsonString(resp); - } - - private string GetCallerIP(Hashtable request) - { - if (!m_Proxy) - return Util.GetCallerIP(request); - - // We're behind a proxy - Hashtable headers = (Hashtable)request["headers"]; - - //// DEBUG - //foreach (object o in headers.Keys) - // m_log.DebugFormat("XXX {0} = {1}", o.ToString(), (headers[o] == null? "null" : headers[o].ToString())); - - string xff = "X-Forwarded-For"; - if (headers.ContainsKey(xff.ToLower())) - xff = xff.ToLower(); - - if (!headers.ContainsKey(xff) || headers[xff] == null) - { - m_log.WarnFormat("[AGENT HANDLER]: No XFF header"); - return Util.GetCallerIP(request); - } - - m_log.DebugFormat("[AGENT HANDLER]: XFF is {0}", headers[xff]); - - IPEndPoint ep = Util.GetClientIPFromXFF((string)headers[xff]); - if (ep != null) - return ep.Address.ToString(); - - // Oops - return Util.GetCallerIP(request); - } - - // subclasses can override this - protected virtual bool CreateAgent(GridRegion destination, AgentCircuitData aCircuit, uint teleportFlags, out string reason) - { - return m_SimulationService.CreateAgent(destination, aCircuit, teleportFlags, out reason); - } - protected void DoAgentPut(Hashtable request, Hashtable responsedata) { OSDMap args = Utils.GetOSDMap((string)request["body"]); @@ -434,4 +324,189 @@ namespace OpenSim.Server.Handlers.Simulation } + public class AgentPostHandler : BaseStreamHandler + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private ISimulationService m_SimulationService; + protected bool m_Proxy = false; + + public AgentPostHandler(ISimulationService service) : + base("POST", "/agent") + { + m_SimulationService = service; + } + + public AgentPostHandler(string path) : + base("POST", path) + { + m_SimulationService = null; + } + + public override byte[] Handle(string path, Stream request, + OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + m_log.DebugFormat("[SIMULATION]: Stream handler called"); + + Hashtable keysvals = new Hashtable(); + Hashtable headervals = new Hashtable(); + + string[] querystringkeys = httpRequest.QueryString.AllKeys; + string[] rHeaders = httpRequest.Headers.AllKeys; + + keysvals.Add("uri", httpRequest.RawUrl); + keysvals.Add("content-type", httpRequest.ContentType); + keysvals.Add("http-method", httpRequest.HttpMethod); + + foreach (string queryname in querystringkeys) + keysvals.Add(queryname, httpRequest.QueryString[queryname]); + + foreach (string headername in rHeaders) + headervals[headername] = httpRequest.Headers[headername]; + + keysvals.Add("headers", headervals); + keysvals.Add("querystringkeys", querystringkeys); + + Stream inputStream; + if (httpRequest.ContentType == "application/x-gzip") + inputStream = new GZipStream(request, CompressionMode.Decompress); + else + inputStream = request; + + Encoding encoding = Encoding.UTF8; + StreamReader reader = new StreamReader(inputStream, encoding); + + string requestBody = reader.ReadToEnd(); + keysvals.Add("body", requestBody); + + httpResponse.StatusCode = 200; + httpResponse.ContentType = "text/html"; + httpResponse.KeepAlive = false; + + Hashtable responsedata = new Hashtable(); + + UUID agentID; + UUID regionID; + string action; + + if (!Utils.GetParams((string)keysvals["uri"], out agentID, out regionID, out action)) + { + m_log.InfoFormat("[AGENT HANDLER]: Invalid parameters for agent message {0}", keysvals["uri"]); + + httpResponse.StatusCode = 404; + + return encoding.GetBytes("false"); + } + + DoAgentPost(keysvals, responsedata, agentID); + + httpResponse.StatusCode = (int)responsedata["int_response_code"]; + return encoding.GetBytes((string)responsedata["str_response_string"]); + } + + protected void DoAgentPost(Hashtable request, Hashtable responsedata, UUID id) + { + OSDMap args = Utils.GetOSDMap((string)request["body"]); + if (args == null) + { + responsedata["int_response_code"] = HttpStatusCode.BadRequest; + responsedata["str_response_string"] = "Bad request"; + return; + } + + // retrieve the input arguments + int x = 0, y = 0; + UUID uuid = UUID.Zero; + string regionname = string.Empty; + uint teleportFlags = 0; + if (args.ContainsKey("destination_x") && args["destination_x"] != null) + Int32.TryParse(args["destination_x"].AsString(), out x); + else + m_log.WarnFormat(" -- request didn't have destination_x"); + if (args.ContainsKey("destination_y") && args["destination_y"] != null) + Int32.TryParse(args["destination_y"].AsString(), out y); + else + m_log.WarnFormat(" -- request didn't have destination_y"); + if (args.ContainsKey("destination_uuid") && args["destination_uuid"] != null) + UUID.TryParse(args["destination_uuid"].AsString(), out uuid); + if (args.ContainsKey("destination_name") && args["destination_name"] != null) + regionname = args["destination_name"].ToString(); + if (args.ContainsKey("teleport_flags") && args["teleport_flags"] != null) + teleportFlags = args["teleport_flags"].AsUInteger(); + + GridRegion destination = new GridRegion(); + destination.RegionID = uuid; + destination.RegionLocX = x; + destination.RegionLocY = y; + destination.RegionName = regionname; + + AgentCircuitData aCircuit = new AgentCircuitData(); + try + { + aCircuit.UnpackAgentCircuitData(args); + } + catch (Exception ex) + { + m_log.InfoFormat("[AGENT HANDLER]: exception on unpacking ChildCreate message {0}", ex.Message); + responsedata["int_response_code"] = HttpStatusCode.BadRequest; + responsedata["str_response_string"] = "Bad request"; + return; + } + + OSDMap resp = new OSDMap(2); + string reason = String.Empty; + + // This is the meaning of POST agent + //m_regionClient.AdjustUserInformation(aCircuit); + //bool result = m_SimulationService.CreateAgent(destination, aCircuit, teleportFlags, out reason); + bool result = CreateAgent(destination, aCircuit, teleportFlags, out reason); + + resp["reason"] = OSD.FromString(reason); + resp["success"] = OSD.FromBoolean(result); + // Let's also send out the IP address of the caller back to the caller (HG 1.5) + resp["your_ip"] = OSD.FromString(GetCallerIP(request)); + + // TODO: add reason if not String.Empty? + responsedata["int_response_code"] = HttpStatusCode.OK; + responsedata["str_response_string"] = OSDParser.SerializeJsonString(resp); + } + + private string GetCallerIP(Hashtable request) + { + if (!m_Proxy) + return Util.GetCallerIP(request); + + // We're behind a proxy + Hashtable headers = (Hashtable)request["headers"]; + + //// DEBUG + //foreach (object o in headers.Keys) + // m_log.DebugFormat("XXX {0} = {1}", o.ToString(), (headers[o] == null? "null" : headers[o].ToString())); + + string xff = "X-Forwarded-For"; + if (headers.ContainsKey(xff.ToLower())) + xff = xff.ToLower(); + + if (!headers.ContainsKey(xff) || headers[xff] == null) + { + m_log.WarnFormat("[AGENT HANDLER]: No XFF header"); + return Util.GetCallerIP(request); + } + + m_log.DebugFormat("[AGENT HANDLER]: XFF is {0}", headers[xff]); + + IPEndPoint ep = Util.GetClientIPFromXFF((string)headers[xff]); + if (ep != null) + return ep.Address.ToString(); + + // Oops + return Util.GetCallerIP(request); + } + + // subclasses can override this + protected virtual bool CreateAgent(GridRegion destination, AgentCircuitData aCircuit, uint teleportFlags, out string reason) + { + return m_SimulationService.CreateAgent(destination, aCircuit, teleportFlags, out reason); + } + } } diff --git a/OpenSim/Server/Handlers/Simulation/SimulationServiceInConnector.cs b/OpenSim/Server/Handlers/Simulation/SimulationServiceInConnector.cs index f33eda7f03..42d8ecaa90 100644 --- a/OpenSim/Server/Handlers/Simulation/SimulationServiceInConnector.cs +++ b/OpenSim/Server/Handlers/Simulation/SimulationServiceInConnector.cs @@ -43,30 +43,15 @@ namespace OpenSim.Server.Handlers.Simulation public SimulationServiceInConnector(IConfigSource config, IHttpServer server, IScene scene) : base(config, server, String.Empty) { - //IConfig serverConfig = config.Configs["SimulationService"]; - //if (serverConfig == null) - // throw new Exception("No section 'SimulationService' in config file"); - - //string simService = serverConfig.GetString("LocalServiceModule", - // String.Empty); - - //if (simService == String.Empty) - // throw new Exception("No SimulationService in config file"); - - //Object[] args = new Object[] { config }; m_LocalSimulationService = scene.RequestModuleInterface(); m_LocalSimulationService = m_LocalSimulationService.GetInnerService(); - //ServerUtils.LoadPlugin(simService, args); - //System.Console.WriteLine("XXXXXXXXXXXXXXXXXXX m_AssetSetvice == null? " + ((m_AssetService == null) ? "yes" : "no")); - //server.AddStreamHandler(new AgentGetHandler(m_SimulationService, m_AuthenticationService)); - //server.AddStreamHandler(new AgentPostHandler(m_SimulationService, m_AuthenticationService)); - //server.AddStreamHandler(new AgentPutHandler(m_SimulationService, m_AuthenticationService)); - //server.AddStreamHandler(new AgentDeleteHandler(m_SimulationService, m_AuthenticationService)); + // This one MUST be a stream handler because compressed fatpacks + // are pure binary and shoehorning that into a string with UTF-8 + // encoding breaks it + server.AddStreamHandler(new AgentPostHandler(m_LocalSimulationService)); server.AddHTTPHandler("/agent/", new AgentHandler(m_LocalSimulationService).Handler); server.AddHTTPHandler("/object/", new ObjectHandler(m_LocalSimulationService).Handler); - - //server.AddStreamHandler(new ObjectPostHandler(m_SimulationService, authentication)); } } } diff --git a/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs b/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs index cef6473b78..3a00c2b2eb 100644 --- a/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs +++ b/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs @@ -102,12 +102,21 @@ namespace OpenSim.Services.Connectors.Simulation args["destination_uuid"] = OSD.FromString(destination.RegionID.ToString()); args["teleport_flags"] = OSD.FromString(flags.ToString()); - OSDMap result = WebUtil.PostToService(uri, args, 20000); + OSDMap result = WebUtil.PostToServiceCompressed(uri, args, 30000); if (result["Success"].AsBoolean()) return true; + result = WebUtil.PostToService(uri, args, 30000); + + if (result["Success"].AsBoolean()) + { + m_log.WarnFormat( + "[REMOTE SIMULATION CONNECTOR]: Remote simulator {0} did not accept compressed transfer, suggest updating it.", destination.RegionName); + return true; + } + m_log.WarnFormat( - "[REMOTE SIMULATION CONNECTOR]: Failed to create agent {0} {1} at remote simulator {1}", + "[REMOTE SIMULATION CONNECTOR]: Failed to create agent {0} {1} at remote simulator {2}", aCircuit.firstname, aCircuit.lastname, destination.RegionName); reason = result["Message"] != null ? result["Message"].AsString() : "error"; return false; @@ -274,7 +283,7 @@ namespace OpenSim.Services.Connectors.Simulation try { - OSDMap result = WebUtil.ServiceOSDRequest(uri, request, "QUERYACCESS", 10000); + OSDMap result = WebUtil.ServiceOSDRequest(uri, request, "QUERYACCESS", 10000, false); bool success = result["success"].AsBoolean(); if (result.ContainsKey("_Result")) { @@ -326,7 +335,7 @@ namespace OpenSim.Services.Connectors.Simulation try { - WebUtil.ServiceOSDRequest(uri, null, "DELETE", 10000); + WebUtil.ServiceOSDRequest(uri, null, "DELETE", 10000, false); } catch (Exception e) { @@ -346,7 +355,7 @@ namespace OpenSim.Services.Connectors.Simulation try { - WebUtil.ServiceOSDRequest(uri, null, "DELETE", 10000); + WebUtil.ServiceOSDRequest(uri, null, "DELETE", 10000, false); } catch (Exception e) { From e345f0389504df9b9cd6fd18ef0c275d3ee8ab0b Mon Sep 17 00:00:00 2001 From: Melanie Date: Sun, 8 May 2011 21:36:51 +0200 Subject: [PATCH 50/78] Remove a spammy debug I left in. Disable TP cancel button at the point of no return. --- .../Framework/EntityTransfer/EntityTransferModule.cs | 3 ++- OpenSim/Server/Handlers/Simulation/AgentHandlers.cs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index da3a5413a6..6e4ec8175a 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -328,7 +328,6 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Let's create an agent there if one doesn't exist yet. bool logout = false; - sp.ControllingClient.SendTeleportProgress(teleportFlags | (uint)TeleportFlags.DisableCancel, "Creating agent..."); if (!CreateAgent(sp, reg, finalDestination, agentCircuit, teleportFlags, out reason, out logout)) { sp.ControllingClient.SendTeleportFailed(String.Format("Destination refused: {0}", @@ -396,6 +395,8 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return; } + sp.ControllingClient.SendTeleportProgress(teleportFlags | (uint)TeleportFlags.DisableCancel, "sending_dest"); + m_log.DebugFormat( "[ENTITY TRANSFER MODULE]: Sending new CAPS seed url {0} to client {1}", capsPath, sp.UUID); diff --git a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs index d52750b4e0..21b26dfd40 100644 --- a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs +++ b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs @@ -90,7 +90,6 @@ namespace OpenSim.Server.Handlers.Simulation // Next, let's parse the verb string method = (string)request["http-method"]; - m_log.DebugFormat("[SIMULATION]: Got verb {0} in HTTP handler", method); if (method.Equals("PUT")) { DoAgentPut(request, responsedata); From c8b955803953e46f5695d3faac1f363406074ffa Mon Sep 17 00:00:00 2001 From: Melanie Date: Sun, 8 May 2011 23:23:33 +0200 Subject: [PATCH 51/78] Also compress the actual fatpacks --- OpenSim/Framework/WebUtil.cs | 5 + .../Handlers/Simulation/AgentHandlers.cs | 264 +++++++++++------- .../SimulationServiceInConnector.cs | 1 + .../Simulation/SimulationServiceConnector.cs | 7 +- 4 files changed, 182 insertions(+), 95 deletions(-) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index 18e50a2849..6ca65ae3ad 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -141,6 +141,11 @@ namespace OpenSim.Framework /// PUT JSON-encoded data to a web service that returns LLSD or /// JSON data /// + public static OSDMap PutToServiceCompressed(string url, OSDMap data, int timeout) + { + return ServiceOSDRequest(url,data, "PUT", timeout, true); + } + public static OSDMap PutToService(string url, OSDMap data, int timeout) { return ServiceOSDRequest(url,data, "PUT", timeout, false); diff --git a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs index 21b26dfd40..379924fa8d 100644 --- a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs +++ b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs @@ -90,12 +90,7 @@ namespace OpenSim.Server.Handlers.Simulation // Next, let's parse the verb string method = (string)request["http-method"]; - if (method.Equals("PUT")) - { - DoAgentPut(request, responsedata); - return responsedata; - } - else if (method.Equals("GET")) + if (method.Equals("GET")) { DoAgentGet(request, responsedata, agentID, regionID); return responsedata; @@ -121,94 +116,6 @@ namespace OpenSim.Server.Handlers.Simulation } - protected void DoAgentPut(Hashtable request, Hashtable responsedata) - { - OSDMap args = Utils.GetOSDMap((string)request["body"]); - if (args == null) - { - responsedata["int_response_code"] = HttpStatusCode.BadRequest; - responsedata["str_response_string"] = "Bad request"; - return; - } - - // retrieve the input arguments - int x = 0, y = 0; - UUID uuid = UUID.Zero; - string regionname = string.Empty; - if (args.ContainsKey("destination_x") && args["destination_x"] != null) - Int32.TryParse(args["destination_x"].AsString(), out x); - if (args.ContainsKey("destination_y") && args["destination_y"] != null) - Int32.TryParse(args["destination_y"].AsString(), out y); - if (args.ContainsKey("destination_uuid") && args["destination_uuid"] != null) - UUID.TryParse(args["destination_uuid"].AsString(), out uuid); - if (args.ContainsKey("destination_name") && args["destination_name"] != null) - regionname = args["destination_name"].ToString(); - - GridRegion destination = new GridRegion(); - destination.RegionID = uuid; - destination.RegionLocX = x; - destination.RegionLocY = y; - destination.RegionName = regionname; - - string messageType; - if (args["message_type"] != null) - messageType = args["message_type"].AsString(); - else - { - m_log.Warn("[AGENT HANDLER]: Agent Put Message Type not found. "); - messageType = "AgentData"; - } - - bool result = true; - if ("AgentData".Equals(messageType)) - { - AgentData agent = new AgentData(); - try - { - agent.Unpack(args, m_SimulationService.GetScene(destination.RegionHandle)); - } - catch (Exception ex) - { - m_log.InfoFormat("[AGENT HANDLER]: exception on unpacking ChildAgentUpdate message {0}", ex.Message); - responsedata["int_response_code"] = HttpStatusCode.BadRequest; - responsedata["str_response_string"] = "Bad request"; - return; - } - - //agent.Dump(); - // This is one of the meanings of PUT agent - result = UpdateAgent(destination, agent); - - } - else if ("AgentPosition".Equals(messageType)) - { - AgentPosition agent = new AgentPosition(); - try - { - agent.Unpack(args, m_SimulationService.GetScene(destination.RegionHandle)); - } - catch (Exception ex) - { - m_log.InfoFormat("[AGENT HANDLER]: exception on unpacking ChildAgentUpdate message {0}", ex.Message); - return; - } - //agent.Dump(); - // This is one of the meanings of PUT agent - result = m_SimulationService.UpdateAgent(destination, agent); - - } - - responsedata["int_response_code"] = HttpStatusCode.OK; - responsedata["str_response_string"] = result.ToString(); - //responsedata["str_response_string"] = OSDParser.SerializeJsonString(resp); ??? instead - } - - // subclasses can override this - protected virtual bool UpdateAgent(GridRegion destination, AgentData agent) - { - return m_SimulationService.UpdateAgent(destination, agent); - } - protected virtual void DoQueryAccess(Hashtable request, Hashtable responsedata, UUID id, UUID regionID) { if (m_SimulationService == null) @@ -508,4 +415,173 @@ namespace OpenSim.Server.Handlers.Simulation return m_SimulationService.CreateAgent(destination, aCircuit, teleportFlags, out reason); } } + + public class AgentPutHandler : BaseStreamHandler + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private ISimulationService m_SimulationService; + protected bool m_Proxy = false; + + public AgentPutHandler(ISimulationService service) : + base("PUT", "/agent") + { + m_SimulationService = service; + } + + public AgentPutHandler(string path) : + base("PUT", path) + { + m_SimulationService = null; + } + + public override byte[] Handle(string path, Stream request, + OSHttpRequest httpRequest, OSHttpResponse httpResponse) + { + m_log.DebugFormat("[SIMULATION]: Stream handler called"); + + Hashtable keysvals = new Hashtable(); + Hashtable headervals = new Hashtable(); + + string[] querystringkeys = httpRequest.QueryString.AllKeys; + string[] rHeaders = httpRequest.Headers.AllKeys; + + keysvals.Add("uri", httpRequest.RawUrl); + keysvals.Add("content-type", httpRequest.ContentType); + keysvals.Add("http-method", httpRequest.HttpMethod); + + foreach (string queryname in querystringkeys) + keysvals.Add(queryname, httpRequest.QueryString[queryname]); + + foreach (string headername in rHeaders) + headervals[headername] = httpRequest.Headers[headername]; + + keysvals.Add("headers", headervals); + keysvals.Add("querystringkeys", querystringkeys); + + Stream inputStream; + if (httpRequest.ContentType == "application/x-gzip") + inputStream = new GZipStream(request, CompressionMode.Decompress); + else + inputStream = request; + + Encoding encoding = Encoding.UTF8; + StreamReader reader = new StreamReader(inputStream, encoding); + + string requestBody = reader.ReadToEnd(); + keysvals.Add("body", requestBody); + + httpResponse.StatusCode = 200; + httpResponse.ContentType = "text/html"; + httpResponse.KeepAlive = false; + + Hashtable responsedata = new Hashtable(); + + UUID agentID; + UUID regionID; + string action; + + if (!Utils.GetParams((string)keysvals["uri"], out agentID, out regionID, out action)) + { + m_log.InfoFormat("[AGENT HANDLER]: Invalid parameters for agent message {0}", keysvals["uri"]); + + httpResponse.StatusCode = 404; + + return encoding.GetBytes("false"); + } + + DoAgentPut(keysvals, responsedata); + + httpResponse.StatusCode = (int)responsedata["int_response_code"]; + return encoding.GetBytes((string)responsedata["str_response_string"]); + } + + protected void DoAgentPut(Hashtable request, Hashtable responsedata) + { + OSDMap args = Utils.GetOSDMap((string)request["body"]); + if (args == null) + { + responsedata["int_response_code"] = HttpStatusCode.BadRequest; + responsedata["str_response_string"] = "Bad request"; + return; + } + + // retrieve the input arguments + int x = 0, y = 0; + UUID uuid = UUID.Zero; + string regionname = string.Empty; + if (args.ContainsKey("destination_x") && args["destination_x"] != null) + Int32.TryParse(args["destination_x"].AsString(), out x); + if (args.ContainsKey("destination_y") && args["destination_y"] != null) + Int32.TryParse(args["destination_y"].AsString(), out y); + if (args.ContainsKey("destination_uuid") && args["destination_uuid"] != null) + UUID.TryParse(args["destination_uuid"].AsString(), out uuid); + if (args.ContainsKey("destination_name") && args["destination_name"] != null) + regionname = args["destination_name"].ToString(); + + GridRegion destination = new GridRegion(); + destination.RegionID = uuid; + destination.RegionLocX = x; + destination.RegionLocY = y; + destination.RegionName = regionname; + + string messageType; + if (args["message_type"] != null) + messageType = args["message_type"].AsString(); + else + { + m_log.Warn("[AGENT HANDLER]: Agent Put Message Type not found. "); + messageType = "AgentData"; + } + + bool result = true; + if ("AgentData".Equals(messageType)) + { + AgentData agent = new AgentData(); + try + { + agent.Unpack(args, m_SimulationService.GetScene(destination.RegionHandle)); + } + catch (Exception ex) + { + m_log.InfoFormat("[AGENT HANDLER]: exception on unpacking ChildAgentUpdate message {0}", ex.Message); + responsedata["int_response_code"] = HttpStatusCode.BadRequest; + responsedata["str_response_string"] = "Bad request"; + return; + } + + //agent.Dump(); + // This is one of the meanings of PUT agent + result = UpdateAgent(destination, agent); + + } + else if ("AgentPosition".Equals(messageType)) + { + AgentPosition agent = new AgentPosition(); + try + { + agent.Unpack(args, m_SimulationService.GetScene(destination.RegionHandle)); + } + catch (Exception ex) + { + m_log.InfoFormat("[AGENT HANDLER]: exception on unpacking ChildAgentUpdate message {0}", ex.Message); + return; + } + //agent.Dump(); + // This is one of the meanings of PUT agent + result = m_SimulationService.UpdateAgent(destination, agent); + + } + + responsedata["int_response_code"] = HttpStatusCode.OK; + responsedata["str_response_string"] = result.ToString(); + //responsedata["str_response_string"] = OSDParser.SerializeJsonString(resp); ??? instead + } + + // subclasses can override this + protected virtual bool UpdateAgent(GridRegion destination, AgentData agent) + { + return m_SimulationService.UpdateAgent(destination, agent); + } + } } diff --git a/OpenSim/Server/Handlers/Simulation/SimulationServiceInConnector.cs b/OpenSim/Server/Handlers/Simulation/SimulationServiceInConnector.cs index 42d8ecaa90..0da08f8a70 100644 --- a/OpenSim/Server/Handlers/Simulation/SimulationServiceInConnector.cs +++ b/OpenSim/Server/Handlers/Simulation/SimulationServiceInConnector.cs @@ -50,6 +50,7 @@ namespace OpenSim.Server.Handlers.Simulation // are pure binary and shoehorning that into a string with UTF-8 // encoding breaks it server.AddStreamHandler(new AgentPostHandler(m_LocalSimulationService)); + server.AddStreamHandler(new AgentPutHandler(m_LocalSimulationService)); server.AddHTTPHandler("/agent/", new AgentHandler(m_LocalSimulationService).Handler); server.AddHTTPHandler("/object/", new ObjectHandler(m_LocalSimulationService).Handler); } diff --git a/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs b/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs index 3a00c2b2eb..f38087383c 100644 --- a/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs +++ b/OpenSim/Services/Connectors/Simulation/SimulationServiceConnector.cs @@ -216,7 +216,12 @@ namespace OpenSim.Services.Connectors.Simulation args["destination_name"] = OSD.FromString(destination.RegionName); args["destination_uuid"] = OSD.FromString(destination.RegionID.ToString()); - OSDMap result = WebUtil.PutToService(uri, args, timeout); + OSDMap result = WebUtil.PutToServiceCompressed(uri, args, timeout); + if (result["Success"].AsBoolean()) + return true; + + result = WebUtil.PutToService(uri, args, timeout); + return result["Success"].AsBoolean(); } catch (Exception e) From a650c74d2353acc3eb872fef80ff9a07b0b491cc Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Sun, 8 May 2011 16:50:36 -0700 Subject: [PATCH 52/78] Comment verbose debug message in GetTexture --- OpenSim/Capabilities/Handlers/GetTexture/GetTextureHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Capabilities/Handlers/GetTexture/GetTextureHandler.cs b/OpenSim/Capabilities/Handlers/GetTexture/GetTextureHandler.cs index 00ff3d0b5b..105a1e028b 100644 --- a/OpenSim/Capabilities/Handlers/GetTexture/GetTextureHandler.cs +++ b/OpenSim/Capabilities/Handlers/GetTexture/GetTextureHandler.cs @@ -73,7 +73,7 @@ namespace OpenSim.Capabilities.Handlers string textureStr = query.GetOne("texture_id"); string format = query.GetOne("format"); - m_log.DebugFormat("[GETTEXTURE]: called {0}", textureStr); + //m_log.DebugFormat("[GETTEXTURE]: called {0}", textureStr); if (m_assetService == null) { From 3a5e841b0b35a9a67538b986394687785c84acb7 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Sun, 8 May 2011 16:51:04 -0700 Subject: [PATCH 53/78] Fix content-type to be application/x-www-form-urlencoded --- OpenSim/Framework/WebUtil.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index 18e50a2849..6b8c3c005e 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -836,7 +836,7 @@ namespace OpenSim.Framework { if ((verb == "POST") || (verb == "PUT")) { - request.ContentType = "text/www-form-urlencoded"; + request.ContentType = "application/x-www-form-urlencoded"; int length = 0; using (StreamWriter writer = new StreamWriter(buffer)) From 2b88d8f93f13bcaa88db13c7c12065c729eed4d1 Mon Sep 17 00:00:00 2001 From: Melanie Date: Mon, 9 May 2011 00:59:32 +0200 Subject: [PATCH 54/78] Add commands to delete objects by name, UUID, creator or owner --- OpenSim/Region/Framework/Scenes/Scene.cs | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 49fbe3397c..76ab299742 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -587,6 +587,19 @@ namespace OpenSim.Region.Framework.Scenes "reload estate", "Reload the estate data", HandleReloadEstate); + MainConsole.Instance.Commands.AddCommand("region", false, "delete object owner", + "delete object owner ", + "Delete object by owner", HandleDeleteObject); + MainConsole.Instance.Commands.AddCommand("region", false, "delete object creator", + "delete object creator ", + "Delete object by creator", HandleDeleteObject); + MainConsole.Instance.Commands.AddCommand("region", false, "delete object uuid", + "delete object uuid ", + "Delete object by uuid", HandleDeleteObject); + MainConsole.Instance.Commands.AddCommand("region", false, "delete object name", + "delete object name ", + "Delete object by name", HandleDeleteObject); + //Bind Storage Manager functions to some land manager functions for this scene EventManager.OnLandObjectAdded += new EventManager.LandObjectAdded(simDataService.StoreLandObject); @@ -4852,6 +4865,60 @@ namespace OpenSim.Region.Framework.Scenes } } + private void HandleDeleteObject(string module, string[] cmd) + { + if (cmd.Length < 4) + return; + + string mode = cmd[2]; + string o = cmd[3]; + + List deletes = new List(); + + UUID match; + + switch (mode) + { + case "owner": + if (!UUID.TryParse(o, out match)) + return; + ForEachSOG(delegate (SceneObjectGroup g) + { + if (g.OwnerID == match && !g.IsAttachment) + deletes.Add(g); + }); + break; + case "creator": + if (!UUID.TryParse(o, out match)) + return; + ForEachSOG(delegate (SceneObjectGroup g) + { + if (g.RootPart.CreatorID == match && !g.IsAttachment) + deletes.Add(g); + }); + break; + case "uuid": + if (!UUID.TryParse(o, out match)) + return; + ForEachSOG(delegate (SceneObjectGroup g) + { + if (g.UUID == match && !g.IsAttachment) + deletes.Add(g); + }); + break; + case "name": + ForEachSOG(delegate (SceneObjectGroup g) + { + if (g.RootPart.Name == o && !g.IsAttachment) + deletes.Add(g); + }); + break; + } + + foreach (SceneObjectGroup g in deletes) + DeleteSceneObject(g, false); + } + private void HandleReloadEstate(string module, string[] cmd) { if (MainConsole.Instance.ConsoleScene == null || From 65d595597de9e28ad219d2fbe2326cc99be0e642 Mon Sep 17 00:00:00 2001 From: "E. Allen Soard" Date: Fri, 6 May 2011 18:48:00 -0700 Subject: [PATCH 55/78] Adds an optional module to enforce prim limits on a given parcel Takes into account acculmitive prim allowance when multiple parcels are owned by the same avatar on the same region. Does not handle prims that are moved by a script or account for temporary objects at the time of creation. other wise handles all tested cases including: Creating a new object from the build menu Moving an object from another parcel duplicating an object via shift move rezing an object from a script --- .../PrimLimitsModule/PrimLimitsModule.cs | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs diff --git a/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs new file mode 100644 index 0000000000..0aee191fac --- /dev/null +++ b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs @@ -0,0 +1,163 @@ +/* + * 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 OpenSimulator 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.Reflection; +using log4net; +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.OptionalModules +{ + /// + /// Simplest possible example of a non-shared region module. + /// + /// + /// This module is the simplest possible example of a non-shared region module (a module where each scene/region + /// in the simulator has its own copy). If anybody wants to create a more complex example in the future then + /// please create a separate class. + /// + /// This module is not active by default. If you want to see it in action, + /// then just uncomment the line below starting with [Extension(Path... + /// + /// When the module is enabled it will print messages when it receives certain events to the screen and the log + /// file. + /// + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "PrimLimitsModule")] + public class BareBonesNonSharedModule : INonSharedRegionModule + { + protected IDialogModule m_dialogModule; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public string Name { get { return "Prim Limits Module"; } } + + public Type ReplaceableInterface { get { return null; } } + + public void Initialise(IConfigSource source) + { + m_log.DebugFormat("[PRIM LIMITS]: INITIALIZED MODULE"); + } + + public void Close() + { + m_log.DebugFormat("[PRIM LIMITS]: CLOSED MODULE"); + } + + public void AddRegion(Scene scene) + { + scene.Permissions.OnRezObject += CanRezObject; + scene.Permissions.OnObjectEntry += CanObjectEnter; + scene.Permissions.OnDuplicateObject += CanDuplicateObject; + m_log.DebugFormat("[PRIM LIMITS]: REGION {0} ADDED", scene.RegionInfo.RegionName); + } + + public void RemoveRegion(Scene scene) + { + scene.Permissions.OnRezObject -= CanRezObject; + scene.Permissions.OnObjectEntry -= CanObjectEnter; + scene.Permissions.OnDuplicateObject -= CanDuplicateObject; + m_log.DebugFormat("[PRIM LIMITS]: REGION {0} REMOVED", scene.RegionInfo.RegionName); + } + + public void RegionLoaded(Scene scene) + { + m_dialogModule = scene.RequestModuleInterface(); + m_log.DebugFormat("[PRIM LIMITS]: REGION {0} LOADED", scene.RegionInfo.RegionName); + } + private bool CanRezObject(int objectCount, UUID owner, Vector3 objectPosition, Scene scene) + { + // This may be a little long winded and can probably be optomized + int usedPrims = scene.LandChannel.GetLandObject(objectPosition.X,objectPosition.Y).PrimCounts.Total; + LandData landData = scene.LandChannel.GetLandObject(objectPosition.X,objectPosition.Y).LandData; + int simulatorCapacity = (int)(((float)landData.SimwideArea / 65536.0f) * + (float)scene.RegionInfo.ObjectCapacity * (float)scene.RegionInfo.RegionSettings.ObjectBonus); + + if(objectCount + usedPrims > simulatorCapacity) + { + m_dialogModule.SendAlertToUser(owner, "Unable to rez object because the parcel is too full"); + return false; + } + + return true; + } + //OnMoveObject + private bool CanObjectEnter(UUID objectID, bool enteringRegion, Vector3 newPoint, Scene scene) + { + SceneObjectPart obj = scene.GetSceneObjectPart(objectID); + Vector3 oldPoint = obj.GroupPosition; + int objectCount = obj.ParentGroup.PrimCount; + ILandObject oldParcel = scene.LandChannel.GetLandObject(oldPoint.X, oldPoint.Y); + ILandObject newParcel = scene.LandChannel.GetLandObject(newPoint.X, newPoint.Y); + + int usedPrims=newParcel.PrimCounts.Total; + LandData landData = newParcel.LandData; + int simulatorCapacity = (int)(((float)landData.SimwideArea / 65536.0f) * + (float)scene.RegionInfo.ObjectCapacity * (float)scene.RegionInfo.RegionSettings.ObjectBonus); + + // The prim hasn't crossed a region boundry so we don't need to worry + // about prim counts here + if(oldParcel.Equals(newParcel)) + { + return true; + } + // Prim counts are determined by the location of the root prim. if we're + // moving a child prim, just let it pass + if(!obj.IsRoot) + { + return true; + } + // Add Special Case here for temporary prims + + if(objectCount + usedPrims > simulatorCapacity) + { + m_dialogModule.SendAlertToUser(obj.OwnerID, "Unable to move object because the destination parcel is too full"); + return false; + } + return true; + } + //OnDuplicateObject + private bool CanDuplicateObject(int objectCount, UUID objectID, UUID owner, Scene scene, Vector3 objectPosition) + { + // This may be a little long winded and can probably be optomized + int usedPrims = scene.LandChannel.GetLandObject(objectPosition.X,objectPosition.Y).PrimCounts.Total; + LandData landData = scene.LandChannel.GetLandObject(objectPosition.X,objectPosition.Y).LandData; + int simulatorCapacity = (int)(((float)landData.SimwideArea / 65536.0f) * + (float)scene.RegionInfo.ObjectCapacity * (float)scene.RegionInfo.RegionSettings.ObjectBonus); + + if(objectCount + usedPrims > simulatorCapacity) + { + m_dialogModule.SendAlertToUser(owner, "Unable to duplicate object because the parcel is too full"); + return false; + } + return true; + } + } +} \ No newline at end of file From 279ed08de17d05cf7825225379398508facbec60 Mon Sep 17 00:00:00 2001 From: "E. Allen Soard" Date: Fri, 6 May 2011 21:22:06 -0700 Subject: [PATCH 56/78] Incremental patch to add config options to enable/disable the prim limits module to OpenSim.ini. EnforcePrimLimits is set to false by default to emulate the current behavior. --- .../PrimLimitsModule/PrimLimitsModule.cs | 20 ++++++++++++++++++- bin/OpenSim.ini.example | 4 ++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs index 0aee191fac..dd02bc7645 100644 --- a/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs +++ b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs @@ -56,13 +56,23 @@ namespace OpenSim.Region.OptionalModules { protected IDialogModule m_dialogModule; private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - + private bool m_enabled; + public string Name { get { return "Prim Limits Module"; } } public Type ReplaceableInterface { get { return null; } } public void Initialise(IConfigSource source) { + IConfig moduleConfig = source.Configs["PrimLimitsModule"]; + if (moduleConfig != null) + { + this.m_enabled = moduleConfig.GetBoolean("EnforcePrimLimits", false); + } + else + { + this.m_enabled = false; + } m_log.DebugFormat("[PRIM LIMITS]: INITIALIZED MODULE"); } @@ -73,6 +83,10 @@ namespace OpenSim.Region.OptionalModules public void AddRegion(Scene scene) { + if(!m_enabled) + { + return; + } scene.Permissions.OnRezObject += CanRezObject; scene.Permissions.OnObjectEntry += CanObjectEnter; scene.Permissions.OnDuplicateObject += CanDuplicateObject; @@ -81,6 +95,10 @@ namespace OpenSim.Region.OptionalModules public void RemoveRegion(Scene scene) { + if(m_enabled) + { + return; + } scene.Permissions.OnRezObject -= CanRezObject; scene.Permissions.OnObjectEntry -= CanObjectEnter; scene.Permissions.OnDuplicateObject -= CanDuplicateObject; diff --git a/bin/OpenSim.ini.example b/bin/OpenSim.ini.example index 13dc9a67ab..c5df0dba58 100644 --- a/bin/OpenSim.ini.example +++ b/bin/OpenSim.ini.example @@ -736,6 +736,10 @@ ;; Enable media on a prim facilities ; Enabled = true; +[PrimLimitsModule] + ;# {EnforcePrimLimits} {} {Enforce parcel prim limits} {true false} false + ;; Enable parcel prim limits. Off by default to emulate pre-existing behavior. + ; EnforcePrimLimits = false [Architecture] ;# {Include-Architecture} {} {Choose one of the following architectures} {config-include/Standalone.ini config-include/StandaloneHypergrid.ini config-include/Grid.ini config-include/GridHypergrid.ini config-include/SimianGrid.ini config-include/HyperSimianGrid.ini} config-include/Standalone.ini From 527b4e7c7ea9b77e4d3d4d9f55d65f019d8212bc Mon Sep 17 00:00:00 2001 From: "E. Allen Soard" Date: Sat, 7 May 2011 11:41:22 -0700 Subject: [PATCH 57/78] Incremental - Just did some cleanup of comments and class name change to clean up from using BareBonesNonShared as a template. --- .../PrimLimitsModule/PrimLimitsModule.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs index dd02bc7645..5bafec38e8 100644 --- a/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs +++ b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs @@ -38,21 +38,13 @@ using OpenSim.Region.Framework.Scenes; namespace OpenSim.Region.OptionalModules { /// - /// Simplest possible example of a non-shared region module. + /// Enables Prim limits for parcel. /// /// - /// This module is the simplest possible example of a non-shared region module (a module where each scene/region - /// in the simulator has its own copy). If anybody wants to create a more complex example in the future then - /// please create a separate class. - /// - /// This module is not active by default. If you want to see it in action, - /// then just uncomment the line below starting with [Extension(Path... - /// - /// When the module is enabled it will print messages when it receives certain events to the screen and the log - /// file. + /// This module selectivly enables parcel prim limits. /// [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "PrimLimitsModule")] - public class BareBonesNonSharedModule : INonSharedRegionModule + public class PrimLimitsModule : INonSharedRegionModule { protected IDialogModule m_dialogModule; private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); From fd66903f74f63a832846c1487745bfd7facd0e86 Mon Sep 17 00:00:00 2001 From: Melanie Date: Mon, 9 May 2011 02:47:40 +0200 Subject: [PATCH 58/78] Convert the prim count module into a permissionsmodule --- .../PrimLimitsModule/PrimLimitsModule.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs index 5bafec38e8..471b90f0e1 100644 --- a/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs +++ b/OpenSim/Region/OptionalModules/PrimLimitsModule/PrimLimitsModule.cs @@ -27,6 +27,7 @@ using System; using System.Reflection; +using System.Collections.Generic; using log4net; using Mono.Addins; using Nini.Config; @@ -50,27 +51,27 @@ namespace OpenSim.Region.OptionalModules private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private bool m_enabled; - public string Name { get { return "Prim Limits Module"; } } + public string Name { get { return "PrimLimitsModule"; } } public Type ReplaceableInterface { get { return null; } } - public void Initialise(IConfigSource source) + public void Initialise(IConfigSource config) { - IConfig moduleConfig = source.Configs["PrimLimitsModule"]; - if (moduleConfig != null) - { - this.m_enabled = moduleConfig.GetBoolean("EnforcePrimLimits", false); - } - else - { - this.m_enabled = false; - } - m_log.DebugFormat("[PRIM LIMITS]: INITIALIZED MODULE"); + IConfig myConfig = config.Configs["Startup"]; + + string permissionModules = myConfig.GetString("permissionmodules", "DefaultPermissionsModule"); + + List modules=new List(permissionModules.Split(',')); + + if(!modules.Contains("PrimLimitsModule")) + return; + + m_log.DebugFormat("[PRIM LIMITS]: Initialized module"); + m_enabled = true; } public void Close() { - m_log.DebugFormat("[PRIM LIMITS]: CLOSED MODULE"); } public void AddRegion(Scene scene) @@ -82,7 +83,7 @@ namespace OpenSim.Region.OptionalModules scene.Permissions.OnRezObject += CanRezObject; scene.Permissions.OnObjectEntry += CanObjectEnter; scene.Permissions.OnDuplicateObject += CanDuplicateObject; - m_log.DebugFormat("[PRIM LIMITS]: REGION {0} ADDED", scene.RegionInfo.RegionName); + m_log.DebugFormat("[PRIM LIMITS]: Region {0} added", scene.RegionInfo.RegionName); } public void RemoveRegion(Scene scene) @@ -94,14 +95,13 @@ namespace OpenSim.Region.OptionalModules scene.Permissions.OnRezObject -= CanRezObject; scene.Permissions.OnObjectEntry -= CanObjectEnter; scene.Permissions.OnDuplicateObject -= CanDuplicateObject; - m_log.DebugFormat("[PRIM LIMITS]: REGION {0} REMOVED", scene.RegionInfo.RegionName); } public void RegionLoaded(Scene scene) { m_dialogModule = scene.RequestModuleInterface(); - m_log.DebugFormat("[PRIM LIMITS]: REGION {0} LOADED", scene.RegionInfo.RegionName); } + private bool CanRezObject(int objectCount, UUID owner, Vector3 objectPosition, Scene scene) { // This may be a little long winded and can probably be optomized @@ -170,4 +170,4 @@ namespace OpenSim.Region.OptionalModules return true; } } -} \ No newline at end of file +} From c0a69bfaab2f945f2f871ce6b93c81931ffc2f9a Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Sun, 8 May 2011 22:50:04 -0700 Subject: [PATCH 59/78] The map is seriously broken. This doesn't fix it, but at least provides one more piece of data that seems to be required -- agent flags, which seem to be different in Viewer 2. WARNING: changes IClientAPI. --- OpenSim/Framework/IClientAPI.cs | 2 +- OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs | 3 +-- .../Region/CoreModules/World/WorldMap/MapSearchModule.cs | 8 ++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/OpenSim/Framework/IClientAPI.cs b/OpenSim/Framework/IClientAPI.cs index 069987bd71..f3ac2df085 100644 --- a/OpenSim/Framework/IClientAPI.cs +++ b/OpenSim/Framework/IClientAPI.cs @@ -77,7 +77,7 @@ namespace OpenSim.Framework public delegate void RequestMapBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag); - public delegate void RequestMapName(IClientAPI remoteClient, string mapName); + public delegate void RequestMapName(IClientAPI remoteClient, string mapName, uint flags); public delegate void TeleportLocationRequest( IClientAPI remoteClient, ulong regionHandle, Vector3 position, Vector3 lookAt, uint flags); diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index 025c6e6984..1635a2dd07 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -8264,13 +8264,12 @@ namespace OpenSim.Region.ClientStack.LindenUDP return true; } #endregion - string mapName = Util.UTF8.GetString(map.NameData.Name, 0, map.NameData.Name.Length - 1); RequestMapName handlerMapNameRequest = OnMapNameRequest; if (handlerMapNameRequest != null) { - handlerMapNameRequest(this, mapName); + handlerMapNameRequest(this, mapName, map.AgentData.Flags); } return true; } diff --git a/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs index f9ef2864e6..c059a5f95c 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs @@ -84,7 +84,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap client.OnMapNameRequest += OnMapNameRequest; } - private void OnMapNameRequest(IClientAPI remoteClient, string mapName) + private void OnMapNameRequest(IClientAPI remoteClient, string mapName, uint flags) { if (mapName.Length < 3) { @@ -139,9 +139,9 @@ namespace OpenSim.Region.CoreModules.World.WorldMap data.Y = 0; blocks.Add(data); - // not sure what the flags do here, but seems to be necessary - // to set to "2" for viewer 2 - remoteClient.SendMapBlock(blocks, 2); + // flags are agent flags sent from the viewer. + // they have different values depending on different viewers, apparently + remoteClient.SendMapBlock(blocks, flags); } // private Scene GetClientScene(IClientAPI client) From 8a5f6dc7a534fcdcfc877a6eac2553b18532e760 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Mon, 9 May 2011 10:25:42 -0700 Subject: [PATCH 60/78] Fixes gray tiles on map search for viewers 1. --- OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs | 1 - .../Region/CoreModules/World/WorldMap/MapSearchModule.cs | 7 +++++-- .../Region/CoreModules/World/WorldMap/WorldMapModule.cs | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index 1635a2dd07..1da9d5e87c 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -1363,7 +1363,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP public void SendMapBlock(List mapBlocks, uint flag) { - MapBlockData[] mapBlocks2 = mapBlocks.ToArray(); int maxsend = 10; diff --git a/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs index c059a5f95c..00959b04c4 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/MapSearchModule.cs @@ -106,7 +106,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap else if (regionInfos.Count == 0 && mapName.StartsWith("http://")) remoteClient.SendAlertMessage("Hyperlink could not be established."); - m_log.DebugFormat("[MAPSEARCHMODULE]: search {0} returned {1} regions", mapName, regionInfos.Count); + m_log.DebugFormat("[MAPSEARCHMODULE]: search {0} returned {1} regions. Flags={2}", mapName, regionInfos.Count, flags); List blocks = new List(); MapBlockData data; @@ -117,7 +117,10 @@ namespace OpenSim.Region.CoreModules.World.WorldMap data = new MapBlockData(); data.Agents = 0; data.Access = info.Access; - data.MapImageId = UUID.Zero; // could use info.TerrainImage but it seems to break viewer2 + if (flags == 2) // V2 sends this + data.MapImageId = UUID.Zero; + else + data.MapImageId = info.TerrainImage; data.Name = info.RegionName; data.RegionFlags = 0; // TODO not used? data.WaterHeight = 0; // not used diff --git a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs index 10949701d9..8073f2d918 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs @@ -205,8 +205,8 @@ namespace OpenSim.Region.CoreModules.World.WorldMap { //try //{ - //m_log.DebugFormat("[MAPLAYER]: request: {0}, path: {1}, param: {2}, agent:{3}", - //request, path, param,agentID.ToString()); + //m_log.DebugFormat("[MAPLAYER]: path: {0}, param: {1}, agent:{2}", + // path, param, agentID.ToString()); // this is here because CAPS map requests work even beyond the 10,000 limit. ScenePresence avatarPresence = null; @@ -784,7 +784,8 @@ namespace OpenSim.Region.CoreModules.World.WorldMap /// public virtual void RequestMapBlocks(IClientAPI remoteClient, int minX, int minY, int maxX, int maxY, uint flag) { - if ((flag & 0x10000) != 0) // user clicked on the map a tile that isn't visible + //m_log.ErrorFormat("[YYY] RequestMapBlocks {0}={1}={2}={3} {4}", minX, minY, maxX, maxY, flag); + if ((flag & 0x10000) != 0) // user clicked on qthe map a tile that isn't visible { List response = new List(); From 7e2b35bbb3be3a82be7bbb2b50099752f0196973 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Mon, 9 May 2011 11:35:41 -0700 Subject: [PATCH 61/78] Fixes gray tiles around +/-4 when user clicks on an empty tile. Affects regions above 4096 primarily. --- OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs | 2 +- OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs index a0ccdc7276..8201031fac 100644 --- a/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs +++ b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs @@ -76,7 +76,7 @@ namespace OpenSim.Region.CoreModules.Hypergrid FillInMap(mapBlocks, minX, minY, maxX, maxY); // - remoteClient.SendMapBlock(mapBlocks, flag); + remoteClient.SendMapBlock(mapBlocks, 0); } diff --git a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs index 8073f2d918..0e1dd195b2 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs @@ -846,7 +846,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap MapBlockFromGridRegion(block, r); mapBlocks.Add(block); } - remoteClient.SendMapBlock(mapBlocks, flag); + remoteClient.SendMapBlock(mapBlocks, 0); } protected void MapBlockFromGridRegion(MapBlockData block, GridRegion r) From e1a4a8d8575926bbd3a201c4002e04880251c8b8 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Mon, 9 May 2011 18:28:16 -0700 Subject: [PATCH 62/78] Changed the Access flag on HGMap to 255 (does not exist) and clarified the other value 254 too. Meanings taken from libomv. --- OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs | 2 +- OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs index 8201031fac..d8681b58f8 100644 --- a/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs +++ b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs @@ -93,7 +93,7 @@ namespace OpenSim.Region.CoreModules.Hypergrid mblock.X = (ushort)x; mblock.Y = (ushort)y; mblock.Name = ""; - mblock.Access = 254; // not here??? + mblock.Access = 255; // means 'simulator does not exist' mblock.MapImageId = UUID.Zero; mapBlocks.Add(mblock); } diff --git a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs index 0e1dd195b2..0cacf2dce1 100644 --- a/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs +++ b/OpenSim/Region/CoreModules/World/WorldMap/WorldMapModule.cs @@ -820,7 +820,7 @@ namespace OpenSim.Region.CoreModules.World.WorldMap MapBlockData block = new MapBlockData(); block.X = (ushort)minX; block.Y = (ushort)minY; - block.Access = 254; // == not there + block.Access = 254; // means 'simulator is offline' response.Add(block); } remoteClient.SendMapBlock(response, 0); From 47735468d2e236a919e4f12d3cdbb8fd903149cb Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Tue, 10 May 2011 09:02:25 -0700 Subject: [PATCH 63/78] Somewhat reverting the last commit. The viewer ignores 255's on tiles that were previously on, so we need to send 254 (offline) explicitly. Also removing the +/-4 for HGMap, because it makes the map flicker. --- OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs index d8681b58f8..5ab334ffaf 100644 --- a/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs +++ b/OpenSim/Region/CoreModules/Hypergrid/HGWorldMapModule.cs @@ -62,8 +62,8 @@ namespace OpenSim.Region.CoreModules.Hypergrid { List mapBlocks = new List(); List regions = m_scene.GridService.GetRegionRange(m_scene.RegionInfo.ScopeID, - (minX - 4) * (int)Constants.RegionSize, (maxX + 4) * (int)Constants.RegionSize, - (minY - 4) * (int)Constants.RegionSize, (maxY + 4) * (int)Constants.RegionSize); + minX * (int)Constants.RegionSize, maxX * (int)Constants.RegionSize, + minY * (int)Constants.RegionSize, maxY * (int)Constants.RegionSize); foreach (GridRegion r in regions) { @@ -93,7 +93,7 @@ namespace OpenSim.Region.CoreModules.Hypergrid mblock.X = (ushort)x; mblock.Y = (ushort)y; mblock.Name = ""; - mblock.Access = 255; // means 'simulator does not exist' + mblock.Access = 254; // means 'simulator is offline'. We need this because the viewer ignores 255's mblock.MapImageId = UUID.Zero; mapBlocks.Add(mblock); } From 5548f837501184d68c352097ad1a1de566e18ca0 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Wed, 11 May 2011 10:21:42 -0700 Subject: [PATCH 64/78] Functional improvement: close the stream. Non-functional: add debug messages to find out why updates are getting an error. WARNING: MASSIVE CONSOLE SPAM ON TPs. --- .../Handlers/Simulation/AgentHandlers.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs index 379924fa8d..f938e4b6ff 100644 --- a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs +++ b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs @@ -461,15 +461,32 @@ namespace OpenSim.Server.Handlers.Simulation Stream inputStream; if (httpRequest.ContentType == "application/x-gzip") + { + m_log.DebugFormat("[XXX]: Update called with application/x-gzip"); inputStream = new GZipStream(request, CompressionMode.Decompress); + } else inputStream = request; Encoding encoding = Encoding.UTF8; StreamReader reader = new StreamReader(inputStream, encoding); - string requestBody = reader.ReadToEnd(); - keysvals.Add("body", requestBody); + try + { + string requestBody = reader.ReadToEnd(); + m_log.DebugFormat("[XXX] body {0}", requestBody); + keysvals.Add("body", requestBody); + } + catch (Exception e) + { + m_log.DebugFormat("[AGENT HANDLER]: Exception readin gzip stream: {0}", e); + httpResponse.StatusCode = (int)HttpStatusCode.BadRequest; + return new byte[0]; + } + finally + { + reader.Close(); + } httpResponse.StatusCode = 200; httpResponse.ContentType = "text/html"; From af2e12d2b4fda54ec028c030c1fe5a87d1950251 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Wed, 11 May 2011 10:43:50 -0700 Subject: [PATCH 65/78] One more debug message. Don't use this unless your name is nebadon. --- OpenSim/Server/Handlers/Simulation/AgentHandlers.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs index f938e4b6ff..46b511deac 100644 --- a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs +++ b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs @@ -466,7 +466,10 @@ namespace OpenSim.Server.Handlers.Simulation inputStream = new GZipStream(request, CompressionMode.Decompress); } else + { + m_log.DebugFormat("[XXX]: Update called with {0}", httpRequest.ContentType); inputStream = request; + } Encoding encoding = Encoding.UTF8; StreamReader reader = new StreamReader(inputStream, encoding); From 7fa2489a6e2391205b17cde24cdb4cab1accc8bd Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Wed, 11 May 2011 13:15:27 -0700 Subject: [PATCH 66/78] Revert "Functional improvement: close the stream. Non-functional: add debug messages to find out why updates are getting an error. WARNING: MASSIVE CONSOLE SPAM ON TPs." This reverts commit 5548f837501184d68c352097ad1a1de566e18ca0. --- .../Handlers/Simulation/AgentHandlers.cs | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs index 46b511deac..ad74b9ba05 100644 --- a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs +++ b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs @@ -461,10 +461,7 @@ namespace OpenSim.Server.Handlers.Simulation Stream inputStream; if (httpRequest.ContentType == "application/x-gzip") - { - m_log.DebugFormat("[XXX]: Update called with application/x-gzip"); inputStream = new GZipStream(request, CompressionMode.Decompress); - } else { m_log.DebugFormat("[XXX]: Update called with {0}", httpRequest.ContentType); @@ -474,22 +471,8 @@ namespace OpenSim.Server.Handlers.Simulation Encoding encoding = Encoding.UTF8; StreamReader reader = new StreamReader(inputStream, encoding); - try - { - string requestBody = reader.ReadToEnd(); - m_log.DebugFormat("[XXX] body {0}", requestBody); - keysvals.Add("body", requestBody); - } - catch (Exception e) - { - m_log.DebugFormat("[AGENT HANDLER]: Exception readin gzip stream: {0}", e); - httpResponse.StatusCode = (int)HttpStatusCode.BadRequest; - return new byte[0]; - } - finally - { - reader.Close(); - } + string requestBody = reader.ReadToEnd(); + keysvals.Add("body", requestBody); httpResponse.StatusCode = 200; httpResponse.ContentType = "text/html"; From ac12ace6f1f837cfe934ddc69438796ad174c84f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 12 May 2011 02:46:13 +0100 Subject: [PATCH 67/78] Prevent viewer 2 from creating a duplicate outfit inventory links. I believe this is safe since there is a 1-1 correspondence between link item and worn item (i.e. you can't be wearing the same item at two spots simultaneously in one outfit). This should stop lots of duplicate links being created when viewer 2 is used. However, this doesn't prevent broken inventory links, which I believe is timing related since the effect is not consistent (e.g. keep relogging and the viewer should end up seeing them correctly) . I think we actually see this problem on viewer 1 as well. It might be easier just to implement the Fetch*2 inventory caps which are documented at http://wiki.secondlife.com/wiki/Inventory_API. WebFetch* has been deprecated by Linden Lab since viewer 2.5.1 and according to the sl wiki, "has numerous bugs". --- .../Framework/Scenes/Scene.Inventory.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs index cd01a05dd9..523b7f5e04 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs @@ -904,11 +904,25 @@ namespace OpenSim.Region.Framework.Scenes } } + /// + /// Link an inventory item to an existing item. + /// + /// + /// + /// + /// + /// + /// + /// + /// /param> + /// private void HandleLinkInventoryItem(IClientAPI remoteClient, UUID transActionID, UUID folderID, uint callbackID, string description, string name, sbyte invType, sbyte type, UUID olditemID) { - m_log.DebugFormat("[AGENT INVENTORY]: Received request to create inventory item link {0} in folder {1} pointing to {2}", name, folderID, olditemID); + m_log.DebugFormat( + "[AGENT INVENTORY]: Received request from {0} to create inventory item link {1} in folder {2} pointing to {3}", + remoteClient.Name, name, folderID, olditemID); if (!Permissions.CanCreateUserInventory(invType, remoteClient.AgentId)) return; @@ -916,7 +930,20 @@ namespace OpenSim.Region.Framework.Scenes ScenePresence presence; if (TryGetScenePresence(remoteClient.AgentId, out presence)) { -// byte[] data = null; + bool linkAlreadyExists = false; + List existingItems = InventoryService.GetFolderItems(remoteClient.AgentId, folderID); + foreach (InventoryItemBase item in existingItems) + if (item.AssetID == olditemID) + linkAlreadyExists = true; + + if (linkAlreadyExists) + { + m_log.WarnFormat( + "[AGENT INVENTORY]: Ignoring request from {0} to create item link {1} in folder {2} pointing to {3} since a link already exists", + remoteClient.Name, name, folderID, olditemID); + + return; + } AssetBase asset = new AssetBase(); asset.FullID = olditemID; From 9988bff9e3074f470ac57cd052674bcf816d7d8f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 12 May 2011 03:18:53 +0100 Subject: [PATCH 68/78] Add a smidgen of method doc about the fact that item links reuse the asset id item slot --- OpenSim/Region/Framework/Scenes/Scene.Inventory.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs index 523b7f5e04..a65ceeb335 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs @@ -907,6 +907,10 @@ namespace OpenSim.Region.Framework.Scenes /// /// Link an inventory item to an existing item. /// + /// + /// The linkee item id is placed in the asset id slot. This appears to be what the viewer expects when + /// it receives inventory information. + /// /// /// /// From e9e4c009b470056dce05cae386860494b0734678 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Wed, 11 May 2011 20:44:03 -0700 Subject: [PATCH 69/78] This makes compression of fatpacks actually work. Previously they always failed. See comment in WebUtil. --- OpenSim/Framework/WebUtil.cs | 14 +++++++------- .../Server/Handlers/Simulation/AgentHandlers.cs | 5 ++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index 55b38cdc64..bc2cd01f08 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -199,14 +199,14 @@ namespace OpenSim.Framework using (GZipStream comp = new GZipStream(ms, CompressionMode.Compress)) { comp.Write(buffer, 0, buffer.Length); - comp.Flush(); - - ms.Seek(0, SeekOrigin.Begin); - - request.ContentLength = ms.Length; //Count bytes to send - using (Stream requestStream = request.GetRequestStream()) - requestStream.Write(ms.ToArray(), 0, (int)ms.Length); + // We need to close the gzip stream before we write it anywhere + // because apparently something important related to gzip compression + // gets written on the strteam upon Dispose() } + byte[] buf = ms.ToArray(); + request.ContentLength = buf.Length; //Count bytes to send + using (Stream requestStream = request.GetRequestStream()) + requestStream.Write(buf, 0, (int)buf.Length); } } else diff --git a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs index ad74b9ba05..e8ae05b8ba 100644 --- a/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs +++ b/OpenSim/Server/Handlers/Simulation/AgentHandlers.cs @@ -283,6 +283,7 @@ namespace OpenSim.Server.Handlers.Simulation StreamReader reader = new StreamReader(inputStream, encoding); string requestBody = reader.ReadToEnd(); + reader.Close(); keysvals.Add("body", requestBody); httpResponse.StatusCode = 200; @@ -463,15 +464,13 @@ namespace OpenSim.Server.Handlers.Simulation if (httpRequest.ContentType == "application/x-gzip") inputStream = new GZipStream(request, CompressionMode.Decompress); else - { - m_log.DebugFormat("[XXX]: Update called with {0}", httpRequest.ContentType); inputStream = request; - } Encoding encoding = Encoding.UTF8; StreamReader reader = new StreamReader(inputStream, encoding); string requestBody = reader.ReadToEnd(); + reader.Close(); keysvals.Add("body", requestBody); httpResponse.StatusCode = 200; From f54a36bd592fec6571ddfda81d12a546583ab123 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Thu, 12 May 2011 17:34:26 -0700 Subject: [PATCH 70/78] Tracking a problem with offline IMs coming in as null list. --- OpenSim/Framework/WebUtil.cs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index bc2cd01f08..2fd31d8e88 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -918,6 +918,10 @@ namespace OpenSim.Framework public class SynchronousRestObjectRequester { + private static readonly ILog m_log = + LogManager.GetLogger( + MethodBase.GetCurrentMethod().DeclaringType); + /// /// Perform a synchronous REST request. /// @@ -972,9 +976,9 @@ namespace OpenSim.Framework } } - try + using (WebResponse resp = request.GetResponse()) { - using (WebResponse resp = request.GetResponse()) + try { if (resp.ContentLength > 0) { @@ -984,10 +988,19 @@ namespace OpenSim.Framework respStream.Close(); } } - } - catch (System.InvalidOperationException) - { - // This is what happens when there is invalid XML + catch (System.InvalidOperationException) + { + // This is what happens when there is invalid XML + try + { + m_log.WarnFormat("[SynchronousRestObjectRequester]: Invalid XML:"); + using (StreamReader sr = new StreamReader(resp.GetResponseStream())) + m_log.WarnFormat("{0}", sr.ReadToEnd()); + } + catch (Exception e) + { } + + } } return deserial; } From 42bfab84b8a8197589bc60167a0f989492a37e17 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Thu, 12 May 2011 18:47:14 -0700 Subject: [PATCH 71/78] Bummer, can't print the data I wanted to see. Printing just the context. --- OpenSim/Framework/WebUtil.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index 2fd31d8e88..0b99e82b46 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -991,15 +991,7 @@ namespace OpenSim.Framework catch (System.InvalidOperationException) { // This is what happens when there is invalid XML - try - { - m_log.WarnFormat("[SynchronousRestObjectRequester]: Invalid XML:"); - using (StreamReader sr = new StreamReader(resp.GetResponseStream())) - m_log.WarnFormat("{0}", sr.ReadToEnd()); - } - catch (Exception e) - { } - + m_log.WarnFormat("[SynchronousRestObjectRequester]: Invalid XML {0} {1}", requestUrl, typeof(TResponse).ToString()); } } return deserial; From 9e310a0c0f9cbe06663fcd28fbd0ade3cb119d3d Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Thu, 12 May 2011 18:53:22 -0700 Subject: [PATCH 72/78] Sequencing the using and try clauses as they were before, but this shows that that obsolete function is not catching 404's as it should... --- OpenSim/Framework/WebUtil.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index 0b99e82b46..e9a1e03d85 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -976,9 +976,9 @@ namespace OpenSim.Framework } } - using (WebResponse resp = request.GetResponse()) + try { - try + using (WebResponse resp = request.GetResponse()) { if (resp.ContentLength > 0) { @@ -988,11 +988,11 @@ namespace OpenSim.Framework respStream.Close(); } } - catch (System.InvalidOperationException) - { - // This is what happens when there is invalid XML - m_log.WarnFormat("[SynchronousRestObjectRequester]: Invalid XML {0} {1}", requestUrl, typeof(TResponse).ToString()); - } + } + catch (System.InvalidOperationException) + { + // This is what happens when there is invalid XML + m_log.WarnFormat("[SynchronousRestObjectRequester]: Invalid XML {0} {1}", requestUrl, typeof(TResponse).ToString()); } return deserial; } From 691283c44eed8343b0aae2bde9d21bd86e9c506a Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Thu, 12 May 2011 19:10:44 -0700 Subject: [PATCH 73/78] One more debug message for offline IMs. --- OpenSim/Framework/WebUtil.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index e9a1e03d85..cd93f71780 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -965,8 +965,9 @@ namespace OpenSim.Framework requestStream = request.GetRequestStream(); requestStream.Write(buffer.ToArray(), 0, length); } - catch (Exception) + catch (Exception e) { + m_log.WarnFormat("[SynchronousRestObjectRequester]: exception in sending data to {0}: {1}", requestUrl, e); return deserial; } finally From 5f9edd195c702fac57ab76bca1c0357bce224868 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 13 May 2011 03:24:19 +0100 Subject: [PATCH 74/78] Fix broken inventory links on viewer 2. It appears that if the viewer requests a folder containing links, we must also send the folders that contain the link targets first. This was tested with Kokua 0.1.0 WIP though I predict it will also work with other viewer 2s --- .../ClientStack/Linden/UDP/LLClientView.cs | 7 ++++++- .../Framework/Scenes/Scene.Inventory.cs | 21 +++++++++++++++++-- .../Framework/Scenes/Scene.PacketHandlers.cs | 4 ++++ bin/OpenSim.exe.config | 2 +- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index 1da9d5e87c..5a2c45c1ad 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -1612,14 +1612,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP currentPacket.ItemData[itemsSent % MAX_ITEMS_PER_PACKET] = CreateItemDataBlock(items[itemsSent++]); else { +// m_log.DebugFormat( +// "[LLCLIENTVIEW]: Sending inventory folder details packet to {0} for folder {1}", Name, folderID); OutPacket(currentPacket, ThrottleOutPacketType.Asset, false); currentPacket = null; } - } if (currentPacket != null) + { +// m_log.DebugFormat( +// "[LLCLIENTVIEW]: Sending inventory folder details packet to {0} for folder {1}", Name, folderID); OutPacket(currentPacket, ThrottleOutPacketType.Asset, false); + } } private InventoryDescendentsPacket.FolderDataBlock CreateFolderDataBlock(InventoryFolderBase folder) diff --git a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs index a65ceeb335..3bf2c2b61e 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs @@ -1382,11 +1382,28 @@ namespace OpenSim.Region.Framework.Scenes InventoryFolderBase containingFolder = new InventoryFolderBase(folder.ID, client.AgentId); containingFolder = InventoryService.GetFolder(containingFolder); - //m_log.DebugFormat("[AGENT INVENTORY]: Sending inventory folder contents ({0} nodes) for \"{1}\" to {2} {3}", - // contents.Folders.Count + contents.Items.Count, containingFolder.Name, client.FirstName, client.LastName); +// m_log.DebugFormat("[AGENT INVENTORY]: Sending inventory folder contents ({0} nodes) for \"{1}\" to {2} {3}", +// contents.Folders.Count + contents.Items.Count, containingFolder.Name, client.FirstName, client.LastName); if (containingFolder != null && containingFolder != null) + { + // If the folder requested contains links, then we need to send those folders first, otherwise the links + // will be broken in the viewer. + HashSet linkedItemFolderIdsToSend = new HashSet(); + foreach (InventoryItemBase item in contents.Items) + { + if (item.AssetType == (int)AssetType.Link) + { + InventoryItemBase linkedItem = InventoryService.GetItem(new InventoryItemBase(item.AssetID)); + linkedItemFolderIdsToSend.Add(linkedItem.Folder); + } + } + + foreach (UUID linkedItemFolderId in linkedItemFolderIdsToSend) + SendInventoryUpdate(client, new InventoryFolderBase(linkedItemFolderId), false, true); + client.SendInventoryFolderDetails(client.AgentId, folder.ID, contents.Items, contents.Folders, containingFolder.Version, fetchFolders, fetchItems); + } } /// diff --git a/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs b/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs index ab567fbcb0..e2d7208ff7 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.PacketHandlers.cs @@ -499,6 +499,10 @@ namespace OpenSim.Region.Framework.Scenes public void HandleFetchInventoryDescendents(IClientAPI remoteClient, UUID folderID, UUID ownerID, bool fetchFolders, bool fetchItems, int sortOrder) { +// m_log.DebugFormat( +// "[USER INVENTORY]: HandleFetchInventoryDescendents() for {0}, folder={1}, fetchFolders={2}, fetchItems={3}, sortOrder={4}", +// remoteClient.Name, folderID, fetchFolders, fetchItems, sortOrder); + if (folderID == UUID.Zero) return; diff --git a/bin/OpenSim.exe.config b/bin/OpenSim.exe.config index eece04092f..4a49fc5631 100755 --- a/bin/OpenSim.exe.config +++ b/bin/OpenSim.exe.config @@ -14,7 +14,7 @@ - + From 301321c8535d702651eb8287bc2312df13562683 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Thu, 12 May 2011 19:56:59 -0700 Subject: [PATCH 75/78] Instrument the heck out of offline messages. THIS IS VERY VERBOSE. --- OpenSim/Framework/WebUtil.cs | 16 ++++++++++++++++ .../InstantMessage/OfflineMessageModule.cs | 3 +++ 2 files changed, 19 insertions(+) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index cd93f71780..511d660a83 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -940,6 +940,8 @@ namespace OpenSim.Framework WebRequest request = WebRequest.Create(requestUrl); request.Method = verb; + m_log.DebugFormat("[XXX] 1"); + if ((verb == "POST") || (verb == "PUT")) { request.ContentType = "text/xml"; @@ -959,11 +961,14 @@ namespace OpenSim.Framework int length = (int)buffer.Length; request.ContentLength = length; + m_log.DebugFormat("[XXX] 2"); Stream requestStream = null; try { requestStream = request.GetRequestStream(); requestStream.Write(buffer.ToArray(), 0, length); + m_log.DebugFormat("[XXX] Wrote to stream ok"); + } catch (Exception e) { @@ -977,6 +982,8 @@ namespace OpenSim.Framework } } + m_log.DebugFormat("[XXX] Getting response now... {0}", requestUrl); + try { using (WebResponse resp = request.GetResponse()) @@ -988,6 +995,9 @@ namespace OpenSim.Framework deserial = (TResponse)deserializer.Deserialize(respStream); respStream.Close(); } + else + m_log.DebugFormat("[SynchronousRestObjectRequester]: Oops! no content found in response stream from {0} {1}", requestUrl, verb); + } } catch (System.InvalidOperationException) @@ -995,6 +1005,12 @@ namespace OpenSim.Framework // This is what happens when there is invalid XML m_log.WarnFormat("[SynchronousRestObjectRequester]: Invalid XML {0} {1}", requestUrl, typeof(TResponse).ToString()); } + catch (Exception e) + { + m_log.WarnFormat("[SynchronousRestObjectRequester]: Exception on response from {0} {1}", requestUrl, e); + } + + m_log.DebugFormat("[XXX] reply is null? {0}", (deserial == null) ? "yes": "no"); return deserial; } } diff --git a/OpenSim/Region/CoreModules/Avatar/InstantMessage/OfflineMessageModule.cs b/OpenSim/Region/CoreModules/Avatar/InstantMessage/OfflineMessageModule.cs index 919ea338f4..321b38b3a3 100644 --- a/OpenSim/Region/CoreModules/Avatar/InstantMessage/OfflineMessageModule.cs +++ b/OpenSim/Region/CoreModules/Avatar/InstantMessage/OfflineMessageModule.cs @@ -178,6 +178,9 @@ namespace OpenSim.Region.CoreModules.Avatar.InstantMessage List msglist = SynchronousRestObjectPoster.BeginPostObject>( "POST", m_RestURL + "/RetrieveMessages/", client.AgentId); + if (msglist == null) + m_log.WarnFormat("[OFFLINE MESSAGING]: WARNING null message list."); + foreach (GridInstantMessage im in msglist) { // client.SendInstantMessage(im); From 1cc70df3b45f188f10c7c835bf89700d629203f0 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Thu, 12 May 2011 20:20:08 -0700 Subject: [PATCH 76/78] One more thing printed out. #OfflineIM --- OpenSim/Framework/WebUtil.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index 511d660a83..8fd34cfa4b 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -940,8 +940,6 @@ namespace OpenSim.Framework WebRequest request = WebRequest.Create(requestUrl); request.Method = verb; - m_log.DebugFormat("[XXX] 1"); - if ((verb == "POST") || (verb == "PUT")) { request.ContentType = "text/xml"; @@ -961,7 +959,6 @@ namespace OpenSim.Framework int length = (int)buffer.Length; request.ContentLength = length; - m_log.DebugFormat("[XXX] 2"); Stream requestStream = null; try { @@ -996,7 +993,7 @@ namespace OpenSim.Framework respStream.Close(); } else - m_log.DebugFormat("[SynchronousRestObjectRequester]: Oops! no content found in response stream from {0} {1}", requestUrl, verb); + m_log.DebugFormat("[SynchronousRestObjectRequester]: Oops! no content found in response stream from {0} {1}, ContentLength = {2}", requestUrl, verb, resp.ContentLength); } } From e39dec6f371bc9c0fbb92e010ee7fd8fbeef7d18 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Thu, 12 May 2011 20:48:24 -0700 Subject: [PATCH 77/78] Hopefully this fixes offline messages. The problem was: the server is not setting the ContentLength of the response. That comes up to OpenSim as ContentLength=-1, which made the existing test fail. --- OpenSim/Framework/WebUtil.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/OpenSim/Framework/WebUtil.cs b/OpenSim/Framework/WebUtil.cs index 8fd34cfa4b..27a646aad1 100644 --- a/OpenSim/Framework/WebUtil.cs +++ b/OpenSim/Framework/WebUtil.cs @@ -964,8 +964,6 @@ namespace OpenSim.Framework { requestStream = request.GetRequestStream(); requestStream.Write(buffer.ToArray(), 0, length); - m_log.DebugFormat("[XXX] Wrote to stream ok"); - } catch (Exception e) { @@ -979,13 +977,11 @@ namespace OpenSim.Framework } } - m_log.DebugFormat("[XXX] Getting response now... {0}", requestUrl); - try { using (WebResponse resp = request.GetResponse()) { - if (resp.ContentLength > 0) + if (resp.ContentLength != 0) { Stream respStream = resp.GetResponseStream(); XmlSerializer deserializer = new XmlSerializer(typeof(TResponse)); @@ -993,7 +989,7 @@ namespace OpenSim.Framework respStream.Close(); } else - m_log.DebugFormat("[SynchronousRestObjectRequester]: Oops! no content found in response stream from {0} {1}, ContentLength = {2}", requestUrl, verb, resp.ContentLength); + m_log.WarnFormat("[SynchronousRestObjectRequester]: Oops! no content found in response stream from {0} {1}", requestUrl, verb); } } @@ -1007,7 +1003,6 @@ namespace OpenSim.Framework m_log.WarnFormat("[SynchronousRestObjectRequester]: Exception on response from {0} {1}", requestUrl, e); } - m_log.DebugFormat("[XXX] reply is null? {0}", (deserial == null) ? "yes": "no"); return deserial; } } From eceedba23b45e338089070e8b059da63082815a1 Mon Sep 17 00:00:00 2001 From: Melanie Date: Sun, 17 Apr 2011 18:28:06 +0200 Subject: [PATCH 78/78] Fix the cert validation handler so that it will not block other parts of the server doing ssl successfully. --- .../Scripting/HttpRequest/ScriptsHttpRequests.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs index 4c8424d7cc..43672d114d 100644 --- a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs +++ b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs @@ -118,7 +118,15 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest return true; } - return chain.Build(new X509Certificate2(certificate)); + if ((((int)sslPolicyErrors) & ~4) != 0) + return false; + + if (ServicePointManager.CertificatePolicy != null) + { + ServicePoint sp = Request.ServicePoint; + return ServicePointManager.CertificatePolicy.CheckValidationResult (sp, certificate, Request, 0); + } + return true; } #region IHttpRequestModule Members @@ -464,4 +472,4 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest } } } -} \ No newline at end of file +}