/*
* 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.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Timers;
using log4net;
using NDesk.Options;
using Nini.Config;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.Console;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Monitoring;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Services.Interfaces;
namespace OpenSim
{
///
/// Interactive OpenSim region server
///
public class OpenSim : OpenSimBase
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
protected string m_startupCommandsFile;
protected string m_shutdownCommandsFile;
protected bool m_gui = false;
protected string m_consoleType = "local";
protected uint m_consolePort = 0;
///
/// Prompt to use for simulator command line.
///
private string m_consolePrompt;
///
/// Regex for parsing out special characters in the prompt.
///
private Regex m_consolePromptRegex = new Regex(@"([^\\])\\(\w)", RegexOptions.Compiled);
private string m_timedScript = "disabled";
private int m_timeInterval = 1200;
private System.Timers.Timer m_scriptTimer;
public OpenSim(IConfigSource configSource) : base(configSource)
{
}
protected override void ReadExtraConfigSettings()
{
base.ReadExtraConfigSettings();
IConfig startupConfig = Config.Configs["Startup"];
IConfig networkConfig = Config.Configs["Network"];
int stpMinThreads = 2;
int stpMaxThreads = 15;
if (startupConfig != null)
{
m_startupCommandsFile = startupConfig.GetString("startup_console_commands_file", "startup_commands.txt");
m_shutdownCommandsFile = startupConfig.GetString("shutdown_console_commands_file", "shutdown_commands.txt");
if (startupConfig.GetString("console", String.Empty) == String.Empty)
m_gui = startupConfig.GetBoolean("gui", false);
else
m_consoleType= startupConfig.GetString("console", String.Empty);
if (networkConfig != null)
m_consolePort = (uint)networkConfig.GetInt("console_port", 0);
m_timedScript = startupConfig.GetString("timer_Script", "disabled");
if (m_timedScript != "disabled")
{
m_timeInterval = startupConfig.GetInt("timer_Interval", 1200);
}
string asyncCallMethodStr = startupConfig.GetString("async_call_method", String.Empty);
FireAndForgetMethod asyncCallMethod;
if (!String.IsNullOrEmpty(asyncCallMethodStr) && Utils.EnumTryParse(asyncCallMethodStr, out asyncCallMethod))
Util.FireAndForgetMethod = asyncCallMethod;
stpMinThreads = startupConfig.GetInt("MinPoolThreads", 2 );
stpMaxThreads = startupConfig.GetInt("MaxPoolThreads", 25);
m_consolePrompt = startupConfig.GetString("ConsolePrompt", @"Region (\R) ");
}
if (Util.FireAndForgetMethod == FireAndForgetMethod.SmartThreadPool)
Util.InitThreadPool(stpMinThreads, stpMaxThreads);
m_log.Info("[OPENSIM MAIN]: Using async_call_method " + Util.FireAndForgetMethod);
}
private static Mono.Unix.UnixSignal[] signals;
private Thread signal_thread = new Thread (delegate ()
{
while (true)
{
// Wait for a signal to be delivered
int index = Mono.Unix.UnixSignal.WaitAny (signals, -1);
//Mono.Unix.Native.Signum signal = signals [index].Signum;
MainConsole.Instance.RunCommand("shutdown");
}
});
///
/// Performs initialisation of the scene, such as loading configuration from disk.
///
protected override void StartupSpecific()
{
m_log.Info("====================================================================");
m_log.Info("========================= STARTING OPENSIM =========================");
m_log.Info("====================================================================");
if(!Util.IsWindows())
{
try
{
// linux mac os specifics
signals = new Mono.Unix.UnixSignal[]
{
new Mono.Unix.UnixSignal(Mono.Unix.Native.Signum.SIGTERM)
};
signal_thread.Start();
}
catch (Exception e)
{
m_log.Info("Could not set up UNIX signal handlers. SIGTERM will not");
m_log.InfoFormat("shut down gracefully: {0}", e.Message);
m_log.Debug("Exception was: ", e);
}
}
//m_log.InfoFormat("[OPENSIM MAIN]: GC Is Server GC: {0}", GCSettings.IsServerGC.ToString());
// http://msdn.microsoft.com/en-us/library/bb384202.aspx
//GCSettings.LatencyMode = GCLatencyMode.Batch;
//m_log.InfoFormat("[OPENSIM MAIN]: GC Latency Mode: {0}", GCSettings.LatencyMode.ToString());
if (m_gui) // Driven by external GUI
{
m_console = new CommandConsole("Region");
}
else
{
switch (m_consoleType)
{
case "basic":
m_console = new CommandConsole("Region");
break;
case "rest":
m_console = new RemoteConsole("Region");
((RemoteConsole)m_console).ReadConfig(Config);
break;
default:
m_console = new LocalConsole("Region", Config.Configs["Startup"]);
break;
}
}
MainConsole.Instance = m_console;
LogEnvironmentInformation();
RegisterCommonAppenders(Config.Configs["Startup"]);
RegisterConsoleCommands();
base.StartupSpecific();
MainServer.Instance.AddStreamHandler(new OpenSim.SimStatusHandler());
MainServer.Instance.AddStreamHandler(new OpenSim.XSimStatusHandler(this));
if (userStatsURI != String.Empty)
MainServer.Instance.AddStreamHandler(new OpenSim.UXSimStatusHandler(this));
MainServer.Instance.AddStreamHandler(new OpenSim.SimRobotsHandler());
if (managedStatsURI != String.Empty)
{
string urlBase = String.Format("/{0}/", managedStatsURI);
StatsManager.StatsPassword = managedStatsPassword;
MainServer.Instance.AddHTTPHandler(urlBase, StatsManager.HandleStatsRequest);
m_log.InfoFormat("[OPENSIM] Enabling remote managed stats fetch. URL = {0}", urlBase);
}
if (m_console is RemoteConsole)
{
if (m_consolePort == 0)
{
((RemoteConsole)m_console).SetServer(m_httpServer);
}
else
{
((RemoteConsole)m_console).SetServer(MainServer.GetHttpServer(m_consolePort));
}
}
// Hook up to the watchdog timer
Watchdog.OnWatchdogTimeout += WatchdogTimeoutHandler;
PrintFileToConsole("startuplogo.txt");
// For now, start at the 'root' level by default
if (SceneManager.Scenes.Count == 1) // If there is only one region, select it
ChangeSelectedRegion("region",
new string[] {"change", "region", SceneManager.Scenes[0].RegionInfo.RegionName});
else
ChangeSelectedRegion("region", new string[] {"change", "region", "root"});
//Run Startup Commands
if (String.IsNullOrEmpty(m_startupCommandsFile))
{
m_log.Info("[STARTUP]: No startup command script specified. Moving on...");
}
else
{
RunCommandScript(m_startupCommandsFile);
}
// Start timer script (run a script every xx seconds)
if (m_timedScript != "disabled")
{
m_scriptTimer = new System.Timers.Timer();
m_scriptTimer.Enabled = true;
m_scriptTimer.Interval = m_timeInterval*1000;
m_scriptTimer.Elapsed += RunAutoTimerScript;
}
}
///
/// Register standard set of region console commands
///
private void RegisterConsoleCommands()
{
MainServer.RegisterHttpConsoleCommands(m_console);
m_console.Commands.AddCommand("Objects", false, "force update",
"force update",
"Force the update of all objects on clients",
HandleForceUpdate);
m_console.Commands.AddCommand("General", false, "change region",
"change region ",
"Change current console region",
ChangeSelectedRegion);
m_console.Commands.AddCommand("Archiving", false, "save xml",
"save xml []",
"Save a region's data in XML format",
SaveXml);
m_console.Commands.AddCommand("Archiving", false, "save xml2",
"save xml2 []",
"Save a region's data in XML2 format",
SaveXml2);
m_console.Commands.AddCommand("Archiving", false, "load xml",
"load xml [ [-newUID [ ]]]",
"Load a region's data from XML format",
LoadXml);
m_console.Commands.AddCommand("Archiving", false, "load xml2",
"load xml2 []",
"Load a region's data from XML2 format",
LoadXml2);
m_console.Commands.AddCommand("Archiving", false, "save prims xml2",
"save prims xml2 [ ]",
"Save named prim to XML2",
SavePrimsXml2);
m_console.Commands.AddCommand("Archiving", false, "load oar",
"load oar [-m|--merge] [-s|--skip-assets]"
+ " [--default-user \"User Name\"]"
+ " [--force-terrain] [--force-parcels]"
+ " [--no-objects]"
+ " [--rotation degrees]"
+ " [--bounding-origin \"\"]"
+ " [--bounding-size \"\"]"
+ " [--displacement \"\"]"
+ " [-d|--debug]"
+ " []",
"Load a region's data from an OAR archive.",
"--merge will merge the OAR with the existing scene (suppresses terrain and parcel info loading).\n"
+ "--skip-assets will load the OAR but ignore the assets it contains.\n"
+ "--default-user will use this user for any objects with an owner whose UUID is not found in the grid.\n"
+ "--force-terrain forces the loading of terrain from the oar (undoes suppression done by --merge).\n"
+ "--force-parcels forces the loading of parcels from the oar (undoes suppression done by --merge).\n"
+ "--no-objects suppresses the addition of any objects (good for loading only the terrain).\n"
+ "--rotation specified rotation to be applied to the oar. Specified in degrees.\n"
+ "--bounding-origin will only place objects that after displacement and rotation fall within the bounding cube who's position starts at . Defaults to <0,0,0>.\n"
+ "--bounding-size specifies the size of the bounding cube. The default is the size of the destination region and cannot be larger than this.\n"
+ "--displacement will add this value to the position of every object loaded.\n"
+ "--debug forces the archiver to display messages about where each object is being placed.\n\n"
+ "The path can be either a filesystem location or a URI.\n"
+ " If this is not given then the command looks for an OAR named region.oar in the current directory."
+ " [--rotation-center \"\"] used to be an option, now it does nothing and will be removed soon."
+ "When an OAR is being loaded, operations are applied in this order:\n"
+ "1: Rotation (around the incoming OARs region center)\n"
+ "2: Cropping (a bounding cube with origin and size)\n"
+ "3: Displacement (setting offset coordinates within the destination region)",
LoadOar); ;
m_console.Commands.AddCommand("Archiving", false, "save oar",
//"save oar [-v|--version=] [-p|--profile=] []",
"save oar [-h|--home=] [--noassets] [--publish] [--perm=] [--all] []",
"Save a region's data to an OAR archive.",
// "-v|--version= generates scene objects as per older versions of the serialization (e.g. -v=0)" + Environment.NewLine
"-h|--home= adds the url of the profile service to the saved user information.\n"
+ "--noassets stops assets being saved to the OAR.\n"
+ "--publish saves an OAR stripped of owner and last owner information.\n"
+ " on reload, the estate owner will be the owner of all objects\n"
+ " this is useful if you're making oars generally available that might be reloaded to the same grid from which you published\n"
+ "--perm= stops objects with insufficient permissions from being saved to the OAR.\n"
+ " can contain one or more of these characters: \"C\" = Copy, \"T\" = Transfer\n"
+ "--all saves all the regions in the simulator, instead of just the current region.\n"
+ "The OAR path must be a filesystem path."
+ " If this is not given then the oar is saved to region.oar in the current directory.",
SaveOar);
m_console.Commands.AddCommand("Objects", false, "edit scale",
"edit scale ",
"Change the scale of a named prim",
HandleEditScale);
m_console.Commands.AddCommand("Objects", false, "rotate scene",
"rotate scene [centerX, centerY]",
"Rotates all scene objects around centerX, centerY (default 128, 128) (please back up your region before using)",
HandleRotateScene);
m_console.Commands.AddCommand("Objects", false, "scale scene",
"scale scene ",
"Scales the scene objects (please back up your region before using)",
HandleScaleScene);
m_console.Commands.AddCommand("Objects", false, "translate scene",
"translate scene xOffset yOffset zOffset",
"translates the scene objects (please back up your region before using)",
HandleTranslateScene);
m_console.Commands.AddCommand("Users", false, "kick user",
"kick user [--force] [message]",
"Kick a user off the simulator",
"The --force option will kick the user without any checks to see whether it's already in the process of closing\n"
+ "Only use this option if you are sure the avatar is inactive and a normal kick user operation does not removed them",
KickUserCommand);
m_console.Commands.AddCommand("Users", false, "show users",
"show users [full]",
"Show user data for users currently on the region",
"Without the 'full' option, only users actually on the region are shown."
+ " With the 'full' option child agents of users in neighbouring regions are also shown.",
HandleShow);
m_console.Commands.AddCommand("Comms", false, "show connections",
"show connections",
"Show connection data",
HandleShow);
m_console.Commands.AddCommand("Comms", false, "show circuits",
"show circuits",
"Show agent circuit data",
HandleShow);
m_console.Commands.AddCommand("Comms", false, "show pending-objects",
"show pending-objects",
"Show # of objects on the pending queues of all scene viewers",
HandleShow);
m_console.Commands.AddCommand("General", false, "show modules",
"show modules",
"Show module data",
HandleShow);
m_console.Commands.AddCommand("Regions", false, "show regions",
"show regions",
"Show region data",
HandleShow);
m_console.Commands.AddCommand("Regions", false, "show ratings",
"show ratings",
"Show rating data",
HandleShow);
m_console.Commands.AddCommand("Objects", false, "backup",
"backup",
"Persist currently unsaved object changes immediately instead of waiting for the normal persistence call.",
RunCommand);
m_console.Commands.AddCommand("Regions", false, "create region",
"create region [\"region name\"] ",
"Create a new region.",
"The settings for \"region name\" are read from . Paths specified with are relative to your Regions directory, unless an absolute path is given."
+ " If \"region name\" does not exist in , it will be added." + Environment.NewLine
+ "Without \"region name\", the first region found in will be created." + Environment.NewLine
+ "If does not exist, it will be created.",
HandleCreateRegion);
m_console.Commands.AddCommand("Regions", false, "restart",
"restart",
"Restart the currently selected region(s) in this instance",
RunCommand);
m_console.Commands.AddCommand("General", false, "command-script",
"command-script