opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 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  
28 using System;
29 using System.Collections.Generic;
30 using System.Diagnostics;
31 using System.IO;
32 using System.Reflection;
33 using System.Timers;
34 using System.Text.RegularExpressions;
35 using log4net;
36 using Mono.Addins;
37 using Nini.Config;
38 using OpenSim.Framework;
39 using OpenSim.Region.Framework.Interfaces;
40 using OpenSim.Region.Framework.Scenes;
41  
42 namespace OpenSim.Region.OptionalModules.World.AutoBackup
43 {
44 /// <summary>
45 /// Choose between ways of naming the backup files that are generated.
46 /// </summary>
47 /// <remarks>Time: OARs are named by a timestamp.
48 /// Sequential: OARs are named by counting (Region_1.oar, Region_2.oar, etc.)
49 /// Overwrite: Only one file per region is created; it's overwritten each time a backup is made.</remarks>
50 public enum NamingType
51 {
52 Time,
53 Sequential,
54 Overwrite
55 }
56  
57 ///<summary>
58 /// AutoBackupModule: save OAR region backups to disk periodically
59 /// </summary>
60 /// <remarks>
61 /// Config Settings Documentation.
62 /// Each configuration setting can be specified in two places: OpenSim.ini or Regions.ini.
63 /// If specified in Regions.ini, the settings should be within the region's section name.
64 /// If specified in OpenSim.ini, the settings should be within the [AutoBackupModule] section.
65 /// Region-specific settings take precedence.
66 ///
67 /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis.
68 /// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings!
69 /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality.
70 /// This is the only required option for enabling auto-backup; the other options have sane defaults.
71 /// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored.
72 /// If False globally (the default), only regions that specifically override it in Regions.ini will get AutoBackup functionality.
73 /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours).
74 /// The number of minutes between each backup attempt.
75 /// If a negative or zero value is given, it is equivalent to setting AutoBackup = False.
76 /// AutoBackupBusyCheck: True/False. Default: True.
77 /// If True, we will only take an auto-backup if a set of conditions are met.
78 /// These conditions are heuristics to try and avoid taking a backup when the sim is busy.
79 /// AutoBackupScript: String. Default: not specified (disabled).
80 /// File path to an executable script or binary to run when an automatic backup is taken.
81 /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary.
82 /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results!
83 /// argv[1] of the executed file/script will be the file name of the generated OAR.
84 /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console.
85 /// AutoBackupNaming: string. Default: Time.
86 /// One of three strings (case insensitive):
87 /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten.
88 /// "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.
89 /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file.
90 /// AutoBackupDir: String. Default: "." (the current directory).
91 /// A directory (absolute or relative) where backups should be saved.
92 /// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass.
93 /// If the time dilation is below this value, don't take a backup right now.
94 /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass.
95 /// If the number of agents is greater than this value, don't take a backup right now
96 /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions.
97 /// Also helps if you don't want AutoBackup at all.
98 /// </remarks>
99 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AutoBackupModule")]
100 public class AutoBackupModule : ISharedRegionModule
101 {
102 private static readonly ILog m_log =
103 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
104 private readonly Dictionary<Guid, IScene> m_pendingSaves = new Dictionary<Guid, IScene>(1);
105 private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState();
106 private readonly Dictionary<IScene, AutoBackupModuleState> m_states =
107 new Dictionary<IScene, AutoBackupModuleState>(1);
108 private readonly Dictionary<Timer, List<IScene>> m_timerMap =
109 new Dictionary<Timer, List<IScene>>(1);
110 private readonly Dictionary<double, Timer> m_timers = new Dictionary<double, Timer>(1);
111  
112 private delegate T DefaultGetter<T>(string settingName, T defaultValue);
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 regionConfig = (scene != null ? scene.Config.Configs[sRegionName] : null);
303 IConfig config = this.m_configSource.Configs["AutoBackupModule"];
304 if (config == null)
305 {
306 // defaultState would be disabled too if the section doesn't exist.
307 state = this.m_defaultState;
308 return state;
309 }
310  
311 bool tmpEnabled = ResolveBoolean("AutoBackup", this.m_defaultState.Enabled, config, regionConfig);
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 this.ResolveDouble("AutoBackupInterval", this.m_defaultState.IntervalMinutes,
336 config, regionConfig) * 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 = ResolveBoolean("AutoBackupBusyCheck",
404 this.m_defaultState.BusyCheck, config, regionConfig);
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 = ResolveString("AutoBackupNaming",
417 this.m_defaultState.NamingType.ToString(), config, regionConfig);
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 = ResolveString("AutoBackupScript",
449 this.m_defaultState.Script, config, regionConfig);
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 = ResolveString("AutoBackupDir", ".", config, regionConfig);
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 /// Helper function for ParseConfig.
496 /// </summary>
497 /// <param name="settingName"></param>
498 /// <param name="defaultValue"></param>
499 /// <param name="global"></param>
500 /// <param name="local"></param>
501 /// <returns></returns>
502 private bool ResolveBoolean(string settingName, bool defaultValue, IConfig global, IConfig local)
503 {
504 if(local != null)
505 {
506 return local.GetBoolean(settingName, global.GetBoolean(settingName, defaultValue));
507 }
508 else
509 {
510 return global.GetBoolean(settingName, defaultValue);
511 }
512 }
513  
514 /// <summary>
515 /// Helper function for ParseConfig.
516 /// </summary>
517 /// <param name="settingName"></param>
518 /// <param name="defaultValue"></param>
519 /// <param name="global"></param>
520 /// <param name="local"></param>
521 /// <returns></returns>
522 private double ResolveDouble(string settingName, double defaultValue, IConfig global, IConfig local)
523 {
524 if (local != null)
525 {
526 return local.GetDouble(settingName, global.GetDouble(settingName, defaultValue));
527 }
528 else
529 {
530 return global.GetDouble(settingName, defaultValue);
531 }
532 }
533  
534 /// <summary>
535 /// Helper function for ParseConfig.
536 /// </summary>
537 /// <param name="settingName"></param>
538 /// <param name="defaultValue"></param>
539 /// <param name="global"></param>
540 /// <param name="local"></param>
541 /// <returns></returns>
542 private int ResolveInt(string settingName, int defaultValue, IConfig global, IConfig local)
543 {
544 if (local != null)
545 {
546 return local.GetInt(settingName, global.GetInt(settingName, defaultValue));
547 }
548 else
549 {
550 return global.GetInt(settingName, defaultValue);
551 }
552 }
553  
554 /// <summary>
555 /// Helper function for ParseConfig.
556 /// </summary>
557 /// <param name="settingName"></param>
558 /// <param name="defaultValue"></param>
559 /// <param name="global"></param>
560 /// <param name="local"></param>
561 /// <returns></returns>
562 private string ResolveString(string settingName, string defaultValue, IConfig global, IConfig local)
563 {
564 if (local != null)
565 {
566 return local.GetString(settingName, global.GetString(settingName, defaultValue));
567 }
568 else
569 {
570 return global.GetString(settingName, defaultValue);
571 }
572 }
573  
574 /// <summary>
575 /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup.
576 /// </summary>
577 /// <param name="sender"></param>
578 /// <param name="e"></param>
579 private void HandleElapsed(object sender, ElapsedEventArgs e)
580 {
581 // TODO: heuristic thresholds are per-region, so we should probably run heuristics once per region
582 // XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to
583 // check whether the region is too busy! Especially on sims with LOTS of regions.
584 // Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible,
585 // but would allow us to be semantically correct while being easier on perf.
586 // Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi...
587 // Alternative 3: Don't support per-region heuristics at all; just accept them as a global only parameter.
588 // Since this is pretty experimental, I haven't decided which alternative makes the most sense.
589 if (this.m_closed)
590 {
591 return;
592 }
593 bool heuristicsRun = false;
594 bool heuristicsPassed = false;
595 if (!this.m_timerMap.ContainsKey((Timer) sender))
596 {
597 m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender);
598 }
599  
600 List<IScene> tmap = this.m_timerMap[(Timer) sender];
601 if (tmap != null && tmap.Count > 0)
602 {
603 foreach (IScene scene in tmap)
604 {
605 AutoBackupModuleState state = this.m_states[scene];
606 bool heuristics = state.BusyCheck;
607  
608 // Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region.
609 if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics)
610 {
611 this.DoRegionBackup(scene);
612 // Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off!
613 }
614 else if (heuristicsRun)
615 {
616 m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
617 scene.RegionInfo.RegionName + " right now.");
618 continue;
619 // Logical Deduction: heuristics are on but haven't been run
620 }
621 else
622 {
623 heuristicsPassed = this.RunHeuristics(scene);
624 heuristicsRun = true;
625 if (!heuristicsPassed)
626 {
627 m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
628 scene.RegionInfo.RegionName + " right now.");
629 continue;
630 }
631 this.DoRegionBackup(scene);
632 }
633 }
634 }
635 }
636  
637 /// <summary>
638 /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable).
639 /// </summary>
640 /// <param name="scene"></param>
641 private void DoRegionBackup(IScene scene)
642 {
643 if (scene.RegionStatus != RegionStatus.Up)
644 {
645 // We won't backup a region that isn't operating normally.
646 m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName +
647 " because its status is " + scene.RegionStatus);
648 return;
649 }
650  
651 AutoBackupModuleState state = this.m_states[scene];
652 IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>();
653 string savePath = BuildOarPath(scene.RegionInfo.RegionName,
654 state.BackupDir,
655 state.NamingType);
656 if (savePath == null)
657 {
658 m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed");
659 return;
660 }
661 Guid guid = Guid.NewGuid();
662 m_pendingSaves.Add(guid, scene);
663 state.LiveRequests.Add(guid, savePath);
664 ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved);
665 iram.ArchiveRegion(savePath, guid, null);
666 }
667  
668 /// <summary>
669 /// Called by the Event Manager when the OnOarFileSaved event is fired.
670 /// </summary>
671 /// <param name="guid"></param>
672 /// <param name="message"></param>
673 void EventManager_OnOarFileSaved(Guid guid, string message)
674 {
675 // Ignore if the OAR save is being done by some other part of the system
676 if (m_pendingSaves.ContainsKey(guid))
677 {
678 AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])];
679 ExecuteScript(abms.Script, abms.LiveRequests[guid]);
680 m_pendingSaves.Remove(guid);
681 abms.LiveRequests.Remove(guid);
682 }
683 }
684  
685 /// <summary>This format may turn out to be too unwieldy to keep...
686 /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID?
687 /// Sequential numbers, right? We support those, too!</summary>
688 private static string GetTimeString()
689 {
690 StringWriter sw = new StringWriter();
691 sw.Write("_");
692 DateTime now = DateTime.Now;
693 sw.Write(now.Year);
694 sw.Write("y_");
695 sw.Write(now.Month);
696 sw.Write("M_");
697 sw.Write(now.Day);
698 sw.Write("d_");
699 sw.Write(now.Hour);
700 sw.Write("h_");
701 sw.Write(now.Minute);
702 sw.Write("m_");
703 sw.Write(now.Second);
704 sw.Write("s");
705 sw.Flush();
706 string output = sw.ToString();
707 sw.Close();
708 return output;
709 }
710  
711 /// <summary>Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error.</summary>
712 private bool RunHeuristics(IScene region)
713 {
714 try
715 {
716 return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region);
717 }
718 catch (Exception e)
719 {
720 m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e);
721 return false;
722 }
723 }
724  
725 /// <summary>
726 /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5),
727 /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR).
728 /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy".
729 /// </summary>
730 /// <param name="region"></param>
731 /// <returns>Returns true if we're not too busy; false means we've got worse time dilation than the threshold.</returns>
732 private bool RunTimeDilationHeuristic(IScene region)
733 {
734 string regionName = region.RegionInfo.RegionName;
735 return region.TimeDilation >=
736 this.m_configSource.Configs["AutoBackupModule"].GetFloat(
737 regionName + ".AutoBackupDilationThreshold", 0.5f);
738 }
739  
740 /// <summary>
741 /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10),
742 /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR).
743 /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy".
744 /// </summary>
745 /// <param name="region"></param>
746 /// <returns>Returns true if we're not too busy; false means we've got more agents on the sim than the threshold.</returns>
747 private bool RunAgentLimitHeuristic(IScene region)
748 {
749 string regionName = region.RegionInfo.RegionName;
750 try
751 {
752 Scene scene = (Scene) region;
753 // TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful...
754 return scene.GetRootAgentCount() <=
755 this.m_configSource.Configs["AutoBackupModule"].GetInt(
756 regionName + ".AutoBackupAgentThreshold", 10);
757 }
758 catch (InvalidCastException ice)
759 {
760 m_log.Debug(
761 "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!",
762 ice);
763 return true;
764 // Non-obstructionist safest answer...
765 }
766 }
767  
768 /// <summary>
769 /// Run the script or executable specified by the "AutoBackupScript" config setting.
770 /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script.
771 /// 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.
772 /// </summary>
773 /// <param name="scriptName"></param>
774 /// <param name="savePath"></param>
775 private static void ExecuteScript(string scriptName, string savePath)
776 {
777 // Do nothing if there's no script.
778 if (scriptName == null || scriptName.Length <= 0)
779 {
780 return;
781 }
782  
783 try
784 {
785 FileInfo fi = new FileInfo(scriptName);
786 if (fi.Exists)
787 {
788 ProcessStartInfo psi = new ProcessStartInfo(scriptName);
789 psi.Arguments = savePath;
790 psi.CreateNoWindow = true;
791 Process proc = Process.Start(psi);
792 proc.ErrorDataReceived += HandleProcErrorDataReceived;
793 }
794 }
795 catch (Exception e)
796 {
797 m_log.Warn(
798 "Exception encountered when trying to run script for oar backup " + savePath, e);
799 }
800 }
801  
802 /// <summary>
803 /// Called if a running script process writes to stderr.
804 /// </summary>
805 /// <param name="sender"></param>
806 /// <param name="e"></param>
807 private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e)
808 {
809 m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName +
810 " is yacking on stderr: " + e.Data);
811 }
812  
813 /// <summary>
814 /// Quickly stop all timers from firing.
815 /// </summary>
816 private void StopAllTimers()
817 {
818 foreach (Timer t in this.m_timerMap.Keys)
819 {
820 t.Close();
821 }
822 this.m_closed = true;
823 }
824  
825 /// <summary>
826 /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType.
827 /// </summary>
828 /// <param name="dirName"></param>
829 /// <param name="regionName"></param>
830 /// <returns></returns>
831 private static string GetNextFile(string dirName, string regionName)
832 {
833 FileInfo uniqueFile = null;
834 long biggestExistingFile = GetNextOarFileNumber(dirName, regionName);
835 biggestExistingFile++;
836 // We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest.
837 uniqueFile =
838 new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" +
839 biggestExistingFile + ".oar");
840 return uniqueFile.FullName;
841 }
842  
843 /// <summary>
844 /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants.
845 /// </summary>
846 /// <param name="regionName">Name of the region to save.</param>
847 /// <param name="baseDir">Absolute or relative path to the directory where the file should reside.</param>
848 /// <param name="naming">The naming scheme for the file name.</param>
849 /// <returns></returns>
850 private static string BuildOarPath(string regionName, string baseDir, NamingType naming)
851 {
852 FileInfo path = null;
853 switch (naming)
854 {
855 case NamingType.Overwrite:
856 path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar");
857 return path.FullName;
858 case NamingType.Time:
859 path =
860 new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName +
861 GetTimeString() + ".oar");
862 return path.FullName;
863 case NamingType.Sequential:
864 // All codepaths in GetNextFile should return a file name ending in .oar
865 path = new FileInfo(GetNextFile(baseDir, regionName));
866 return path.FullName;
867 default:
868 m_log.Warn("VERY BAD: Unhandled case element " + naming);
869 break;
870 }
871  
872 return null;
873 }
874  
875 /// <summary>
876 /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile).
877 /// </summary>
878 /// <param name="dirName"></param>
879 /// <param name="regionName"></param>
880 /// <returns></returns>
881 private static long GetNextOarFileNumber(string dirName, string regionName)
882 {
883 long retval = 1;
884  
885 DirectoryInfo di = new DirectoryInfo(dirName);
886 FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly);
887 Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name));
888  
889 if (fi.LongLength > 0)
890 {
891 long subtract = 1L;
892 bool worked = false;
893 Regex reg = new Regex(regionName + "_([0-9])+" + ".oar");
894  
895 while (!worked && subtract <= fi.LongLength)
896 {
897 // Pick the file with the last natural ordering
898 string biggestFileName = fi[fi.LongLength - subtract].Name;
899 MatchCollection matches = reg.Matches(biggestFileName);
900 long l = 1;
901 if (matches.Count > 0 && matches[0].Groups.Count > 0)
902 {
903 try
904 {
905 long.TryParse(matches[0].Groups[1].Value, out l);
906 retval = l;
907 worked = true;
908 }
909 catch (FormatException fe)
910 {
911 m_log.Warn(
912 "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!",
913 fe);
914 subtract++;
915 }
916 }
917 else
918 {
919 subtract++;
920 }
921 }
922 }
923 return retval;
924 }
925 }
926 }
927  
928