aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/OptionalModules
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/OptionalModules')
-rw-r--r--OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml1
-rw-r--r--OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs848
-rw-r--r--OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs126
3 files changed, 975 insertions, 0 deletions
diff --git a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml
index 5eea286..8691343 100644
--- a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml
+++ b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml
@@ -13,5 +13,6 @@
13 <RegionModule id="Concierge" type="OpenSim.Region.OptionalModules.Avatar.Concierge.ConciergeModule" /> 13 <RegionModule id="Concierge" type="OpenSim.Region.OptionalModules.Avatar.Concierge.ConciergeModule" />
14 <RegionModule id="VivoxVoice" type="OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice.VivoxVoiceModule" /> 14 <RegionModule id="VivoxVoice" type="OpenSim.Region.OptionalModules.Avatar.Voice.VivoxVoice.VivoxVoiceModule" />
15 <RegionModule id="WorldViewModule" type="OpenSim.Region.OptionalModules.World.WorldView.WorldViewModule" /> 15 <RegionModule id="WorldViewModule" type="OpenSim.Region.OptionalModules.World.WorldView.WorldViewModule" />
16 <RegionModule id="AutoBackupModule" type="OpenSim.Region.OptionalModules.World.AutoBackup.AutoBackupModule" />
16 </Extension> 17 </Extension>
17</Addin> 18</Addin>
diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
new file mode 100644
index 0000000..4a9615d
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
@@ -0,0 +1,848 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.Diagnostics;
31using System.IO;
32using System.Reflection;
33using System.Timers;
34using System.Text.RegularExpressions;
35using log4net;
36using Nini.Config;
37using OpenSim.Framework;
38using OpenSim.Region.Framework.Interfaces;
39using OpenSim.Region.Framework.Scenes;
40
41namespace OpenSim.Region.OptionalModules.World.AutoBackup
42{
43 /// <summary>
44 /// Choose between ways of naming the backup files that are generated.
45 /// </summary>
46 /// <remarks>Time: OARs are named by a timestamp.
47 /// Sequential: OARs are named by counting (Region_1.oar, Region_2.oar, etc.)
48 /// Overwrite: Only one file per region is created; it's overwritten each time a backup is made.</remarks>
49 public enum NamingType
50 {
51 Time,
52 Sequential,
53 Overwrite
54 }
55
56 ///<summary>
57 /// AutoBackupModule: save OAR region backups to disk periodically
58 /// </summary>
59 /// <remarks>
60 /// Config Settings Documentation.
61 /// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options:
62 /// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section.
63 /// IMPORTANT: You may optionally specify the key name as follows for a per-region key: [Region Name].[Key Name]
64 /// Example: My region is named Foo.
65 /// 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.
66 /// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions.
67 /// Region-specific settings take precedence.
68 ///
69 /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis.
70 /// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings!
71 /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality.
72 /// This is the only required option for enabling auto-backup; the other options have sane defaults.
73 /// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored.
74 /// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality.
75 /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours).
76 /// The number of minutes between each backup attempt.
77 /// If a negative or zero value is given, it is equivalent to setting AutoBackup = False.
78 /// AutoBackupBusyCheck: True/False. Default: True.
79 /// If True, we will only take an auto-backup if a set of conditions are met.
80 /// These conditions are heuristics to try and avoid taking a backup when the sim is busy.
81 /// AutoBackupScript: String. Default: not specified (disabled).
82 /// File path to an executable script or binary to run when an automatic backup is taken.
83 /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary.
84 /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results!
85 /// argv[1] of the executed file/script will be the file name of the generated OAR.
86 /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console.
87 /// AutoBackupNaming: string. Default: Time.
88 /// One of three strings (case insensitive):
89 /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten.
90 /// "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.
91 /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file.
92 /// AutoBackupDir: String. Default: "." (the current directory).
93 /// A directory (absolute or relative) where backups should be saved.
94 /// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass.
95 /// If the time dilation is below this value, don't take a backup right now.
96 /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass.
97 /// If the number of agents is greater than this value, don't take a backup right now
98 /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions.
99 /// Also helps if you don't want AutoBackup at all.
100 /// </remarks>
101 public class AutoBackupModule : ISharedRegionModule
102 {
103 private static readonly ILog m_log =
104 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
105 private readonly Dictionary<Guid, IScene> m_pendingSaves = new Dictionary<Guid, IScene>(1);
106 private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState();
107 private readonly Dictionary<IScene, AutoBackupModuleState> m_states =
108 new Dictionary<IScene, AutoBackupModuleState>(1);
109 private readonly Dictionary<Timer, List<IScene>> m_timerMap =
110 new Dictionary<Timer, List<IScene>>(1);
111 private readonly Dictionary<double, Timer> m_timers = new Dictionary<double, Timer>(1);
112
113 private bool m_enabled;
114
115 /// <summary>
116 /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState!
117 /// </summary>
118 private bool m_closed;
119
120 private IConfigSource m_configSource;
121
122 /// <summary>
123 /// Required by framework.
124 /// </summary>
125 public bool IsSharedModule
126 {
127 get { return true; }
128 }
129
130 #region ISharedRegionModule Members
131
132 /// <summary>
133 /// Identifies the module to the system.
134 /// </summary>
135 string IRegionModuleBase.Name
136 {
137 get { return "AutoBackupModule"; }
138 }
139
140 /// <summary>
141 /// We don't implement an interface, this is a single-use module.
142 /// </summary>
143 Type IRegionModuleBase.ReplaceableInterface
144 {
145 get { return null; }
146 }
147
148 /// <summary>
149 /// Called once in the lifetime of the module at startup.
150 /// </summary>
151 /// <param name="source">The input config source for OpenSim.ini.</param>
152 void IRegionModuleBase.Initialise(IConfigSource source)
153 {
154 // Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module
155 this.m_configSource = source;
156 IConfig moduleConfig = source.Configs["AutoBackupModule"];
157 if (moduleConfig == null)
158 {
159 this.m_enabled = false;
160 return;
161 }
162 else
163 {
164 this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false);
165 if (this.m_enabled)
166 {
167 m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled");
168 }
169 else
170 {
171 return;
172 }
173 }
174
175 Timer defTimer = new Timer(43200000);
176 this.m_defaultState.Timer = defTimer;
177 this.m_timers.Add(43200000, defTimer);
178 defTimer.Elapsed += this.HandleElapsed;
179 defTimer.AutoReset = true;
180 defTimer.Start();
181
182 AutoBackupModuleState abms = this.ParseConfig(null, true);
183 m_log.Debug("[AUTO BACKUP]: Here is the default config:");
184 m_log.Debug(abms.ToString());
185 }
186
187 /// <summary>
188 /// Called once at de-init (sim shutting down).
189 /// </summary>
190 void IRegionModuleBase.Close()
191 {
192 if (!this.m_enabled)
193 {
194 return;
195 }
196
197 // We don't want any timers firing while the sim's coming down; strange things may happen.
198 this.StopAllTimers();
199 }
200
201 /// <summary>
202 /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded.
203 /// </summary>
204 /// <param name="scene"></param>
205 void IRegionModuleBase.AddRegion(Scene scene)
206 {
207 }
208
209 /// <summary>
210 /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene.
211 /// </summary>
212 /// <param name="scene">The scene (region) to stop performing AutoBackup on.</param>
213 void IRegionModuleBase.RemoveRegion(Scene scene)
214 {
215 if (!this.m_enabled)
216 {
217 return;
218 }
219
220 if (this.m_states.ContainsKey(scene))
221 {
222 AutoBackupModuleState abms = this.m_states[scene];
223
224 // Remove this scene out of the timer map list
225 Timer timer = abms.Timer;
226 List<IScene> list = this.m_timerMap[timer];
227 list.Remove(scene);
228
229 // Shut down the timer if this was the last scene for the timer
230 if (list.Count == 0)
231 {
232 this.m_timerMap.Remove(timer);
233 this.m_timers.Remove(timer.Interval);
234 timer.Close();
235 }
236 this.m_states.Remove(scene);
237 }
238 }
239
240 /// <summary>
241 /// Most interesting/complex code paths in AutoBackup begin here.
242 /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc.
243 /// </summary>
244 /// <param name="scene">The scene to (possibly) perform AutoBackup on.</param>
245 void IRegionModuleBase.RegionLoaded(Scene scene)
246 {
247 if (!this.m_enabled)
248 {
249 return;
250 }
251
252 // This really ought not to happen, but just in case, let's pretend it didn't...
253 if (scene == null)
254 {
255 return;
256 }
257
258 AutoBackupModuleState abms = this.ParseConfig(scene, false);
259 m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
260 m_log.Debug((abms == null ? "DEFAULT" : abms.ToString()));
261 }
262
263 /// <summary>
264 /// Currently a no-op.
265 /// </summary>
266 void ISharedRegionModule.PostInitialise()
267 {
268 }
269
270 #endregion
271
272 /// <summary>
273 /// Set up internal state for a given scene. Fairly complex code.
274 /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene.
275 /// </summary>
276 /// <param name="scene">The scene to look at.</param>
277 /// <param name="parseDefault">Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings).</param>
278 /// <returns>An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region.</returns>
279 private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault)
280 {
281 string sRegionName;
282 string sRegionLabel;
283 string prepend;
284 AutoBackupModuleState state;
285
286 if (parseDefault)
287 {
288 sRegionName = null;
289 sRegionLabel = "DEFAULT";
290 prepend = "";
291 state = this.m_defaultState;
292 }
293 else
294 {
295 sRegionName = scene.RegionInfo.RegionName;
296 sRegionLabel = sRegionName;
297 prepend = sRegionName + ".";
298 state = null;
299 }
300
301 // Read the config settings and set variables.
302 IConfig config = this.m_configSource.Configs["AutoBackupModule"];
303 if (config == null)
304 {
305 // defaultState would be disabled too if the section doesn't exist.
306 state = this.m_defaultState;
307 m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled.");
308 return state;
309 }
310
311 bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled);
312 if (state == null && tmpEnabled != this.m_defaultState.Enabled)
313 //Varies from default state
314 {
315 state = new AutoBackupModuleState();
316 }
317
318 if (state != null)
319 {
320 state.Enabled = tmpEnabled;
321 }
322
323 // If you don't want AutoBackup, we stop.
324 if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled))
325 {
326 return state;
327 }
328 else
329 {
330 m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED.");
331 }
332
333 // Borrow an existing timer if one exists for the same interval; otherwise, make a new one.
334 double interval =
335 config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)*
336 60000.0;
337 if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0)
338 {
339 state = new AutoBackupModuleState();
340 }
341
342 if (this.m_timers.ContainsKey(interval))
343 {
344 if (state != null)
345 {
346 state.Timer = this.m_timers[interval];
347 }
348 m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " +
349 sRegionLabel);
350 }
351 else
352 {
353 // 0 or negative interval == do nothing.
354 if (interval <= 0.0 && state != null)
355 {
356 state.Enabled = false;
357 return state;
358 }
359 if (state == null)
360 {
361 state = new AutoBackupModuleState();
362 }
363 Timer tim = new Timer(interval);
364 state.Timer = tim;
365 //Milliseconds -> minutes
366 this.m_timers.Add(interval, tim);
367 tim.Elapsed += this.HandleElapsed;
368 tim.AutoReset = true;
369 tim.Start();
370 }
371
372 // Add the current region to the list of regions tied to this timer.
373 if (scene != null)
374 {
375 if (state != null)
376 {
377 if (this.m_timerMap.ContainsKey(state.Timer))
378 {
379 this.m_timerMap[state.Timer].Add(scene);
380 }
381 else
382 {
383 List<IScene> scns = new List<IScene>(1);
384 scns.Add(scene);
385 this.m_timerMap.Add(state.Timer, scns);
386 }
387 }
388 else
389 {
390 if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer))
391 {
392 this.m_timerMap[this.m_defaultState.Timer].Add(scene);
393 }
394 else
395 {
396 List<IScene> scns = new List<IScene>(1);
397 scns.Add(scene);
398 this.m_timerMap.Add(this.m_defaultState.Timer, scns);
399 }
400 }
401 }
402
403 bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck",
404 this.m_defaultState.BusyCheck);
405 if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck)
406 {
407 state = new AutoBackupModuleState();
408 }
409
410 if (state != null)
411 {
412 state.BusyCheck = tmpBusyCheck;
413 }
414
415 // Set file naming algorithm
416 string stmpNamingType = config.GetString(prepend + "AutoBackupNaming",
417 this.m_defaultState.NamingType.ToString());
418 NamingType tmpNamingType;
419 if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
420 {
421 tmpNamingType = NamingType.Time;
422 }
423 else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
424 {
425 tmpNamingType = NamingType.Sequential;
426 }
427 else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
428 {
429 tmpNamingType = NamingType.Overwrite;
430 }
431 else
432 {
433 m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " +
434 stmpNamingType);
435 tmpNamingType = NamingType.Time;
436 }
437
438 if (state == null && tmpNamingType != this.m_defaultState.NamingType)
439 {
440 state = new AutoBackupModuleState();
441 }
442
443 if (state != null)
444 {
445 state.NamingType = tmpNamingType;
446 }
447
448 string tmpScript = config.GetString(prepend + "AutoBackupScript",
449 this.m_defaultState.Script);
450 if (state == null && tmpScript != this.m_defaultState.Script)
451 {
452 state = new AutoBackupModuleState();
453 }
454
455 if (state != null)
456 {
457 state.Script = tmpScript;
458 }
459
460 string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", ".");
461 if (state == null && tmpBackupDir != this.m_defaultState.BackupDir)
462 {
463 state = new AutoBackupModuleState();
464 }
465
466 if (state != null)
467 {
468 state.BackupDir = tmpBackupDir;
469 // Let's give the user some convenience and auto-mkdir
470 if (state.BackupDir != ".")
471 {
472 try
473 {
474 DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir);
475 if (!dirinfo.Exists)
476 {
477 dirinfo.Create();
478 }
479 }
480 catch (Exception e)
481 {
482 m_log.Warn(
483 "BAD NEWS. You won't be able to save backups to directory " +
484 state.BackupDir +
485 " because it doesn't exist or there's a permissions issue with it. Here's the exception.",
486 e);
487 }
488 }
489 }
490
491 return state;
492 }
493
494 /// <summary>
495 /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup.
496 /// </summary>
497 /// <param name="sender"></param>
498 /// <param name="e"></param>
499 private void HandleElapsed(object sender, ElapsedEventArgs e)
500 {
501 // TODO: heuristic thresholds are per-region, so we should probably run heuristics once per region
502 // XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to
503 // check whether the region is too busy! Especially on sims with LOTS of regions.
504 // Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible,
505 // but would allow us to be semantically correct while being easier on perf.
506 // Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi...
507 // Alternative 3: Don't support per-region heuristics at all; just accept them as a global only parameter.
508 // Since this is pretty experimental, I haven't decided which alternative makes the most sense.
509 if (this.m_closed)
510 {
511 return;
512 }
513 bool heuristicsRun = false;
514 bool heuristicsPassed = false;
515 if (!this.m_timerMap.ContainsKey((Timer) sender))
516 {
517 m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender);
518 }
519
520 List<IScene> tmap = this.m_timerMap[(Timer) sender];
521 if (tmap != null && tmap.Count > 0)
522 {
523 foreach (IScene scene in tmap)
524 {
525 AutoBackupModuleState state = this.m_states[scene];
526 bool heuristics = state.BusyCheck;
527
528 // Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region.
529 if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics)
530 {
531 this.DoRegionBackup(scene);
532 // Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off!
533 }
534 else if (heuristicsRun)
535 {
536 m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
537 scene.RegionInfo.RegionName + " right now.");
538 continue;
539 // Logical Deduction: heuristics are on but haven't been run
540 }
541 else
542 {
543 heuristicsPassed = this.RunHeuristics(scene);
544 heuristicsRun = true;
545 if (!heuristicsPassed)
546 {
547 m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
548 scene.RegionInfo.RegionName + " right now.");
549 continue;
550 }
551 this.DoRegionBackup(scene);
552 }
553 }
554 }
555 }
556
557 /// <summary>
558 /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable).
559 /// </summary>
560 /// <param name="scene"></param>
561 private void DoRegionBackup(IScene scene)
562 {
563 if (scene.RegionStatus != RegionStatus.Up)
564 {
565 // We won't backup a region that isn't operating normally.
566 m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName +
567 " because its status is " + scene.RegionStatus);
568 return;
569 }
570
571 AutoBackupModuleState state = this.m_states[scene];
572 IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>();
573 string savePath = BuildOarPath(scene.RegionInfo.RegionName,
574 state.BackupDir,
575 state.NamingType);
576 if (savePath == null)
577 {
578 m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed");
579 return;
580 }
581 Guid guid = Guid.NewGuid();
582 m_pendingSaves.Add(guid, scene);
583 state.LiveRequests.Add(guid, savePath);
584 ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved);
585 iram.ArchiveRegion(savePath, guid, null);
586 }
587
588 /// <summary>
589 /// Called by the Event Manager when the OnOarFileSaved event is fired.
590 /// </summary>
591 /// <param name="guid"></param>
592 /// <param name="message"></param>
593 void EventManager_OnOarFileSaved(Guid guid, string message)
594 {
595 // Ignore if the OAR save is being done by some other part of the system
596 if (m_pendingSaves.ContainsKey(guid))
597 {
598 AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])];
599 ExecuteScript(abms.Script, abms.LiveRequests[guid]);
600 m_pendingSaves.Remove(guid);
601 abms.LiveRequests.Remove(guid);
602 }
603 }
604
605 /// <summary>This format may turn out to be too unwieldy to keep...
606 /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID?
607 /// Sequential numbers, right? We support those, too!</summary>
608 private static string GetTimeString()
609 {
610 StringWriter sw = new StringWriter();
611 sw.Write("_");
612 DateTime now = DateTime.Now;
613 sw.Write(now.Year);
614 sw.Write("y_");
615 sw.Write(now.Month);
616 sw.Write("M_");
617 sw.Write(now.Day);
618 sw.Write("d_");
619 sw.Write(now.Hour);
620 sw.Write("h_");
621 sw.Write(now.Minute);
622 sw.Write("m_");
623 sw.Write(now.Second);
624 sw.Write("s");
625 sw.Flush();
626 string output = sw.ToString();
627 sw.Close();
628 return output;
629 }
630
631 /// <summary>Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error.</summary>
632 private bool RunHeuristics(IScene region)
633 {
634 try
635 {
636 return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region);
637 }
638 catch (Exception e)
639 {
640 m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e);
641 return false;
642 }
643 }
644
645 /// <summary>
646 /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5),
647 /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR).
648 /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy".
649 /// </summary>
650 /// <param name="region"></param>
651 /// <returns>Returns true if we're not too busy; false means we've got worse time dilation than the threshold.</returns>
652 private bool RunTimeDilationHeuristic(IScene region)
653 {
654 string regionName = region.RegionInfo.RegionName;
655 return region.TimeDilation >=
656 this.m_configSource.Configs["AutoBackupModule"].GetFloat(
657 regionName + ".AutoBackupDilationThreshold", 0.5f);
658 }
659
660 /// <summary>
661 /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10),
662 /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR).
663 /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy".
664 /// </summary>
665 /// <param name="region"></param>
666 /// <returns>Returns true if we're not too busy; false means we've got more agents on the sim than the threshold.</returns>
667 private bool RunAgentLimitHeuristic(IScene region)
668 {
669 string regionName = region.RegionInfo.RegionName;
670 try
671 {
672 Scene scene = (Scene) region;
673 // TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful...
674 return scene.GetRootAgentCount() <=
675 this.m_configSource.Configs["AutoBackupModule"].GetInt(
676 regionName + ".AutoBackupAgentThreshold", 10);
677 }
678 catch (InvalidCastException ice)
679 {
680 m_log.Debug(
681 "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!",
682 ice);
683 return true;
684 // Non-obstructionist safest answer...
685 }
686 }
687
688 /// <summary>
689 /// Run the script or executable specified by the "AutoBackupScript" config setting.
690 /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script.
691 /// 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.
692 /// </summary>
693 /// <param name="scriptName"></param>
694 /// <param name="savePath"></param>
695 private static void ExecuteScript(string scriptName, string savePath)
696 {
697 // Do nothing if there's no script.
698 if (scriptName == null || scriptName.Length <= 0)
699 {
700 return;
701 }
702
703 try
704 {
705 FileInfo fi = new FileInfo(scriptName);
706 if (fi.Exists)
707 {
708 ProcessStartInfo psi = new ProcessStartInfo(scriptName);
709 psi.Arguments = savePath;
710 psi.CreateNoWindow = true;
711 Process proc = Process.Start(psi);
712 proc.ErrorDataReceived += HandleProcErrorDataReceived;
713 }
714 }
715 catch (Exception e)
716 {
717 m_log.Warn(
718 "Exception encountered when trying to run script for oar backup " + savePath, e);
719 }
720 }
721
722 /// <summary>
723 /// Called if a running script process writes to stderr.
724 /// </summary>
725 /// <param name="sender"></param>
726 /// <param name="e"></param>
727 private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e)
728 {
729 m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName +
730 " is yacking on stderr: " + e.Data);
731 }
732
733 /// <summary>
734 /// Quickly stop all timers from firing.
735 /// </summary>
736 private void StopAllTimers()
737 {
738 foreach (Timer t in this.m_timerMap.Keys)
739 {
740 t.Close();
741 }
742 this.m_closed = true;
743 }
744
745 /// <summary>
746 /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType.
747 /// </summary>
748 /// <param name="dirName"></param>
749 /// <param name="regionName"></param>
750 /// <returns></returns>
751 private static string GetNextFile(string dirName, string regionName)
752 {
753 FileInfo uniqueFile = null;
754 long biggestExistingFile = GetNextOarFileNumber(dirName, regionName);
755 biggestExistingFile++;
756 // We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest.
757 uniqueFile =
758 new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" +
759 biggestExistingFile + ".oar");
760 return uniqueFile.FullName;
761 }
762
763 /// <summary>
764 /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants.
765 /// </summary>
766 /// <param name="regionName">Name of the region to save.</param>
767 /// <param name="baseDir">Absolute or relative path to the directory where the file should reside.</param>
768 /// <param name="naming">The naming scheme for the file name.</param>
769 /// <returns></returns>
770 private static string BuildOarPath(string regionName, string baseDir, NamingType naming)
771 {
772 FileInfo path = null;
773 switch (naming)
774 {
775 case NamingType.Overwrite:
776 path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar");
777 return path.FullName;
778 case NamingType.Time:
779 path =
780 new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName +
781 GetTimeString() + ".oar");
782 return path.FullName;
783 case NamingType.Sequential:
784 // All codepaths in GetNextFile should return a file name ending in .oar
785 path = new FileInfo(GetNextFile(baseDir, regionName));
786 return path.FullName;
787 default:
788 m_log.Warn("VERY BAD: Unhandled case element " + naming);
789 break;
790 }
791
792 return null;
793 }
794
795 /// <summary>
796 /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile).
797 /// </summary>
798 /// <param name="dirName"></param>
799 /// <param name="regionName"></param>
800 /// <returns></returns>
801 private static long GetNextOarFileNumber(string dirName, string regionName)
802 {
803 long retval = 1;
804
805 DirectoryInfo di = new DirectoryInfo(dirName);
806 FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly);
807 Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name));
808
809 if (fi.LongLength > 0)
810 {
811 long subtract = 1L;
812 bool worked = false;
813 Regex reg = new Regex(regionName + "_([0-9])+" + ".oar");
814
815 while (!worked && subtract <= fi.LongLength)
816 {
817 // Pick the file with the last natural ordering
818 string biggestFileName = fi[fi.LongLength - subtract].Name;
819 MatchCollection matches = reg.Matches(biggestFileName);
820 long l = 1;
821 if (matches.Count > 0 && matches[0].Groups.Count > 0)
822 {
823 try
824 {
825 long.TryParse(matches[0].Groups[1].Value, out l);
826 retval = l;
827 worked = true;
828 }
829 catch (FormatException fe)
830 {
831 m_log.Warn(
832 "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!",
833 fe);
834 subtract++;
835 }
836 }
837 else
838 {
839 subtract++;
840 }
841 }
842 }
843 return retval;
844 }
845 }
846}
847
848
diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs
new file mode 100644
index 0000000..2db718c
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs
@@ -0,0 +1,126 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30
31
32namespace OpenSim.Region.OptionalModules.World.AutoBackup
33{
34 /// <summary>AutoBackupModuleState: Auto-Backup state for one region (scene).
35 /// If you use this class in any way outside of AutoBackupModule, you should treat the class as opaque.
36 /// Since it is not part of the framework, you really should not rely upon it outside of the AutoBackupModule implementation.
37 /// </summary>
38 ///
39 public class AutoBackupModuleState
40 {
41 private Dictionary<Guid, string> m_liveRequests = null;
42
43 public AutoBackupModuleState()
44 {
45 this.Enabled = false;
46 this.BackupDir = ".";
47 this.BusyCheck = true;
48 this.Timer = null;
49 this.NamingType = NamingType.Time;
50 this.Script = null;
51 }
52
53 public Dictionary<Guid, string> LiveRequests
54 {
55 get {
56 return this.m_liveRequests ??
57 (this.m_liveRequests = new Dictionary<Guid, string>(1));
58 }
59 }
60
61 public bool Enabled
62 {
63 get;
64 set;
65 }
66
67 public System.Timers.Timer Timer
68 {
69 get;
70 set;
71 }
72
73 public double IntervalMinutes
74 {
75 get
76 {
77 if (this.Timer == null)
78 {
79 return -1.0;
80 }
81 else
82 {
83 return this.Timer.Interval / 60000.0;
84 }
85 }
86 }
87
88 public bool BusyCheck
89 {
90 get;
91 set;
92 }
93
94 public string Script
95 {
96 get;
97 set;
98 }
99
100 public string BackupDir
101 {
102 get;
103 set;
104 }
105
106 public NamingType NamingType
107 {
108 get;
109 set;
110 }
111
112 public new string ToString()
113 {
114 string retval = "";
115
116 retval += "[AUTO BACKUP]: AutoBackup: " + (Enabled ? "ENABLED" : "DISABLED") + "\n";
117 retval += "[AUTO BACKUP]: Interval: " + IntervalMinutes + " minutes" + "\n";
118 retval += "[AUTO BACKUP]: Do Busy Check: " + (BusyCheck ? "Yes" : "No") + "\n";
119 retval += "[AUTO BACKUP]: Naming Type: " + NamingType.ToString() + "\n";
120 retval += "[AUTO BACKUP]: Backup Dir: " + BackupDir + "\n";
121 retval += "[AUTO BACKUP]: Script: " + Script + "\n";
122 return retval;
123 }
124 }
125}
126