4 // Copyright (C) 2004-2006 Novell, Inc.
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
28 using System
.Collections
;
30 using System
.Reflection
;
31 using System
.Runtime
.InteropServices
;
32 using System
.Threading
;
38 #if ENABLE_WEBSERVICES
39 using Beagle
.WebService
;
42 namespace Beagle
.Daemon
{
45 public static Thread MainLoopThread
= null;
47 private static Server server
= null;
49 private static bool arg_replace
= false;
50 private static bool arg_disable_scheduler
= false;
51 private static bool arg_indexing_test_mode
= false;
53 public static bool StartServer ()
55 Logger
.Log
.Debug ("Starting messaging server");
57 server
= new Server ("socket");
59 } catch (InvalidOperationException
) {
66 public static void ReplaceExisting ()
68 Logger
.Log
.Info ("Attempting to replace another beagled.");
71 ShutdownRequest request
= new ShutdownRequest ();
72 Logger
.Log
.Info ("Sending Shutdown");
74 // Give it a second to shut down the messaging server
76 } while (! StartServer ());
79 private static void LogMemoryUsage ()
81 while (! Shutdown
.ShutdownRequested
) {
82 int vm_size
= SystemInformation
.VmSize
;
83 int vm_rss
= SystemInformation
.VmRss
;
85 Logger
.Log
.Debug ("Memory usage: VmSize={0:.0} MB, VmRSS={1:.0} MB, GC.GetTotalMemory={2}",
86 vm_size
/1024.0, vm_rss
/1024.0, GC
.GetTotalMemory (false));
88 if (vm_size
> 300 * 1024) {
89 Logger
.Log
.Debug ("VmSize too large --- shutting down");
90 Shutdown
.BeginShutdown ();
97 private static void PrintUsage ()
100 "beagled: The daemon to the Beagle search system.\n" +
101 "Web page: http://beagle-project.org\n" +
102 "Copyright (C) 2004-2006 Novell, Inc.\n\n";
105 "Usage: beagled [OPTIONS]\n\n" +
107 " --foreground, --fg\tRun the daemon in the foreground.\n" +
108 " --background, --bg\tRun the daemon in the background.\n" +
109 " --replace\t\tReplace a running daemon with a new instance.\n" +
110 " --debug\t\tWrite out debugging information.\n" +
111 " --debug-memory\tWrite out debugging information about memory use.\n" +
112 " --indexing-test-mode\tRun in foreground, and exit when fully indexed.\n" +
113 " --indexing-delay\tTime to wait before indexing. (Default 60 seconds)\n" +
114 " --backend [+-]name\t--backend name: only start backend 'name'\n" +
115 " \t--backend +name: also start backend 'name' besides those mentioned in config\n" +
116 " \t--backend -name: dont start backend 'name' if it is enabled in config\n" +
117 " --allow-backend\t(DEPRECATED) Start only the specific backend.\n" +
118 " --deny-backend\t(DEPRECATED) Deny a specific backend.\n" +
119 " --list-backends\tList all the available backends.\n" +
120 " --add-static-backend\tAdd a static backend by path.\n" +
121 " --disable-scheduler\tDisable the use of the scheduler.\n" +
122 " --help\t\tPrint this usage message.\n";
124 #if ENABLE_WEBSERVICES
126 " --web-global\t\tAllow global access to the Web & WebService interfaces.\n" +
127 " --web-port\t\tPort to use for the internal web server.\n" +
128 " --web-root\t\tRoot directory to use for the internal web server.\n" +
129 " --web-disable\t\tDisable Web & WebServices functionality.\n";
132 Console
.WriteLine (usage
);
135 public static bool StartupProcess ()
137 // Profile our initialization
138 Stopwatch stopwatch
= new Stopwatch ();
141 SetupSignalHandlers ();
143 // Fire up our server
144 if (! StartServer ()) {
147 #if ENABLE_WEBSERVICES
148 WebServiceBackEnd
.Stop();
153 Logger
.Log
.Error ("Could not set up the listener for beagle requests. "
154 + "There is probably another beagled instance running. "
155 + "Use --replace to replace the running service");
156 Environment
.Exit (1);
160 // Set up out-of-process indexing
161 if (Environment
.GetEnvironmentVariable ("BEAGLE_ENABLE_IN_PROCESS_INDEXING") == null)
162 LuceneQueryable
.IndexerHook
= new LuceneQueryable
.IndexerCreator (RemoteIndexer
.NewRemoteIndexer
);
164 // Initialize syncronization to keep the indexes local if PathFinder.HomeDir
165 // is on a non-block device, or if BEAGLE_SYNCHRONIZE_LOCALLY is set
166 if ((! SystemInformation
.IsPathOnBlockDevice (PathFinder
.HomeDir
) && Conf
.Daemon
.IndexSynchronization
) ||
167 Environment
.GetEnvironmentVariable ("BEAGLE_SYNCHRONIZE_LOCALLY") != null)
168 IndexSynchronization
.Initialize ();
170 // Start the query driver.
171 Logger
.Log
.Debug ("Starting QueryDriver");
172 QueryDriver
.Start ();
174 // Start the Global Scheduler thread
175 if (! arg_disable_scheduler
) {
176 Logger
.Log
.Debug ("Starting Scheduler thread");
177 Scheduler
.Global
.Start ();
180 // Start our Inotify threads
183 // Test if the FileAdvise stuff is working: This will print a
184 // warning if not. The actual advice calls will fail silently.
185 FileAdvise
.TestAdvise ();
187 #if ENABLE_WEBSERVICES
188 //Beagle Web, WebService access initialization code:
189 WebServiceBackEnd
.Start();
191 Shutdown
.ShutdownEvent
+= OnShutdown
;
193 Conf
.WatchForUpdates ();
197 Logger
.Log
.Debug ("Daemon initialization finished after {0}", stopwatch
);
199 if (arg_indexing_test_mode
) {
200 Thread
.Sleep (1000); // Ugly paranoia: wait a second for the backends to settle.
201 Logger
.Log
.Debug ("Running in indexing test mode");
202 Scheduler
.Global
.EmptyQueueEvent
+= OnEmptySchedulerQueue
;
203 Scheduler
.Global
.Add (null); // pulse the scheduler
208 static void OnEmptySchedulerQueue ()
210 Logger
.Log
.Debug ("Scheduler queue is empty: terminating immediately");
211 Shutdown
.BeginShutdown ();
212 Environment
.Exit (0); // Ugly work-around: We need to call Exit here to avoid deadlocking.
215 public static void Main (string[] args
)
219 } catch (Exception ex
) {
220 Logger
.Log
.Error ("Unhandled exception thrown. Exiting immediately.");
221 Logger
.Log
.Error (ex
);
222 Environment
.Exit (1);
226 public static void DoMain (string[] args
)
228 SystemInformation
.SetProcessName ("beagled");
230 // Process the command-line arguments
231 bool arg_debug
= false;
232 bool arg_debug_memory
= false;
236 while (i
< args
.Length
) {
238 string arg
= args
[i
];
240 string next_arg
= i
< args
.Length
? args
[i
] : null;
246 Environment
.Exit (0);
250 // Silently ignore the --heap-buddy argument: it gets handled
251 // in the wrapper script.
254 case "--list-backends":
255 Console
.WriteLine ("Current available backends:");
256 Console
.Write (QueryDriver
.ListBackends ());
257 Environment
.Exit (0);
278 case "--debug-memory":
280 arg_debug_memory
= true;
283 case "--indexing-test-mode":
284 arg_indexing_test_mode
= true;
289 if (next_arg
== null) {
290 Console
.WriteLine ("--backend requires a backend name");
291 Environment
.Exit (1);
295 if (next_arg
.StartsWith ("--")) {
296 Console
.WriteLine ("--backend requires a backend name. Invalid name '{0}'", next_arg
);
297 Environment
.Exit (1);
301 if (next_arg
[0] != '+' && next_arg
[0] != '-')
302 QueryDriver
.OnlyAllow (next_arg
);
304 if (next_arg
[0] == '+')
305 QueryDriver
.Allow (next_arg
.Substring (1));
307 QueryDriver
.Deny (next_arg
.Substring (1));
310 ++i
; // we used next_arg
313 case "--allow-backend":
314 // --allow-backend is deprecated, use --backends 'name' instead
315 // it will disable reading the list of enabled/disabled backends
316 // from conf and start the backend given
317 if (next_arg
!= null)
318 QueryDriver
.OnlyAllow (next_arg
);
319 ++i
; // we used next_arg
322 case "--deny-backend":
323 // deprecated: use --backends -'name' instead
324 if (next_arg
!= null)
325 QueryDriver
.Deny (next_arg
);
326 ++i
; // we used next_arg
329 case "--add-static-backend":
330 if (next_arg
!= null)
331 QueryDriver
.AddStaticQueryable (next_arg
);
335 case "--disable-scheduler":
336 arg_disable_scheduler
= true;
339 case "--indexing-delay":
340 if (next_arg
!= null) {
342 QueryDriver
.IndexingDelay
= Int32
.Parse (next_arg
);
344 Console
.WriteLine ("'{0}' is not a valid number of seconds", next_arg
);
345 Environment
.Exit (1);
352 case "--autostarted":
353 if (! Conf
.Searching
.Autostart
) {
354 Console
.WriteLine ("Autostarting is disabled, not starting");
355 Environment
.Exit (0);
358 #if ENABLE_WEBSERVICES
360 WebServiceBackEnd
.web_global
= true;
361 WebServiceBackEnd
.web_start
= true;
365 WebServiceBackEnd
.web_port
= next_arg
;
367 WebServiceBackEnd
.web_start
= true;
371 WebServiceBackEnd
.web_rootDir
= next_arg
;
373 WebServiceBackEnd
.web_start
= true;
376 case "--web-disable":
377 WebServiceBackEnd
.web_start
= false;
381 Console
.WriteLine ("Unknown argument '{0}'", arg
);
382 Environment
.Exit (1);
388 if (arg_indexing_test_mode
) {
389 LuceneQueryable
.OptimizeRightAway
= true;
393 // Bail out if we are trying to run as root
394 if (Environment
.UserName
== "root" && Environment
.GetEnvironmentVariable ("SUDO_USER") != null) {
395 Console
.WriteLine ("You appear to be running beagle using sudo. This can cause problems with");
396 Console
.WriteLine ("permissions in your .beagle and .wapi directories if you later try to run");
397 Console
.WriteLine ("as an unprivileged user. If you need to run beagle as root, please use");
398 Console
.WriteLine ("'su -c' instead.");
399 Environment
.Exit (-1);
402 if (Environment
.UserName
== "root" && ! Conf
.Daemon
.AllowRoot
) {
403 Console
.WriteLine ("You can not run beagle as root. Beagle is designed to run from your own");
404 Console
.WriteLine ("user account. If you want to create multiuser or system-wide indexes, use");
405 Console
.WriteLine ("the beagle-build-index tool.");
406 Console
.WriteLine ();
407 Console
.WriteLine ("You can override this setting using the beagle-config or beagle-settings tools.");
408 Environment
.Exit (-1);
412 string tmp
= PathFinder
.HomeDir
;
413 } catch (Exception e
) {
414 Console
.WriteLine ("Unable to start the daemon: {0}", e
.Message
);
415 Environment
.Exit (-1);
418 MainLoopThread
= Thread
.CurrentThread
;
420 Log
.Initialize (PathFinder
.LogDir
,
422 // FIXME: We always turn on full debugging output! We are still
423 // debugging this code, after all...
424 //arg_debug ? LogLevel.Debug : LogLevel.Warn,
428 Logger
.Log
.Info ("Starting Beagle Daemon (version {0})", ExternalStringsHack
.Version
);
430 Logger
.Log
.Info ("Running on {0}", SystemInformation
.MonoRuntimeVersion
);
432 Logger
.Log
.Debug ("Command Line: {0}",
433 Environment
.CommandLine
!= null ? Environment
.CommandLine
: "(null)");
435 if (! ExtendedAttribute
.Supported
) {
436 Logger
.Log
.Warn ("Extended attributes are not supported on this filesystem. " +
437 "Performance will suffer as a result.");
440 // Start our memory-logging thread
441 if (arg_debug_memory
)
442 ExceptionHandlingThread
.Start (new ThreadStart (LogMemoryUsage
));
444 // Do BEAGLE_EXERCISE_THE_DOG_HARDER-related processing.
445 ExerciseTheDogHarder ();
447 if (Application
.InitCheck ("beagled", ref args
))
448 Logger
.Log
.Debug ("Established a connection to the X server");
450 Logger
.Log
.Debug ("Unable to establish a connection to the X server");
452 XSetIOErrorHandler (BeagleXIOErrorHandler
);
454 // Defer all actual startup until the main loop is
455 // running. That way shutdowns during the startup
456 // process work correctly.
457 GLib
.Idle
.Add (new GLib
.IdleHandler (StartupProcess
));
459 // Start our event loop.
460 Logger
.Log
.Debug ("Starting main loop");
464 // If we placed our sockets in a temp directory, try to clean it up
465 // Note: this may fail because the helper is still running
466 if (PathFinder
.GetRemoteStorageDir (false) != PathFinder
.StorageDir
) {
468 Directory
.Delete (PathFinder
.GetRemoteStorageDir (false));
469 } catch (IOException
) { }
472 Logger
.Log
.Debug ("Leaving BeagleDaemon.Main");
476 ExceptionHandlingThread
.SpewLiveThreads ();
480 /////////////////////////////////////////////////////////////////////////////
482 private delegate int XIOErrorHandler (IntPtr display
);
484 [DllImport ("libX11.so.6")]
485 extern static private int XSetIOErrorHandler (XIOErrorHandler handler
);
487 private static int BeagleXIOErrorHandler (IntPtr display
)
489 Logger
.Log
.Debug ("Lost our connection to the X server! Trying to shut down gracefully");
491 if (! Shutdown
.ShutdownRequested
)
492 Shutdown
.BeginShutdown ();
494 Logger
.Log
.Debug ("Xlib is forcing us to exit!");
496 ExceptionHandlingThread
.SpewLiveThreads ();
498 // Returning will cause xlib to exit immediately.
502 /////////////////////////////////////////////////////////////////////////////
504 static void SetupSignalHandlers ()
506 // Force OurSignalHandler to be JITed
507 OurSignalHandler (-1);
509 // Set up our signal handler
510 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGINT
, OurSignalHandler
);
511 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGTERM
, OurSignalHandler
);
512 if (Environment
.GetEnvironmentVariable("BEAGLE_THERE_BE_NO_QUITTIN") == null)
513 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGQUIT
, OurSignalHandler
);
516 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGPIPE
, Mono
.Unix
.Native
.Stdlib
.SIG_IGN
);
519 // Our handler triggers an orderly shutdown when it receives a signal.
520 // However, this can be annoying if the process gets wedged during
521 // shutdown. To deal with that case, we make a note of the time when
522 // the first signal comes in, and we allow signals to unconditionally
523 // kill the process after 5 seconds have passed.
524 static DateTime signal_time
= DateTime
.MinValue
;
525 static void OurSignalHandler (int signal
)
527 // This allows us to call OurSignalHandler w/o doing anything.
528 // We want to call it once to ensure that it is pre-JITed.
532 Logger
.Log
.Debug ("Handling signal {0} ({1})", signal
, (Mono
.Unix
.Native
.Signum
) signal
);
534 bool first_signal
= false;
535 if (signal_time
== DateTime
.MinValue
) {
536 signal_time
= DateTime
.Now
;
540 if (Shutdown
.ShutdownRequested
) {
543 Logger
.Log
.Debug ("Shutdown already in progress.");
545 double t
= (DateTime
.Now
- signal_time
).TotalSeconds
;
546 const double min_t
= 5;
549 Logger
.Log
.Debug ("Signals can force an immediate shutdown in {0:0.00}s", min_t
-t
);
551 Logger
.Log
.Debug ("Forcing immediate shutdown.");
552 Environment
.Exit (0);
557 Logger
.Log
.Debug ("Initiating shutdown in response to signal.");
558 Shutdown
.BeginShutdown ();
562 /////////////////////////////////////////////////////////////////////////////
564 private static void OnShutdown ()
566 #if ENABLE_WEBSERVICES
567 WebServiceBackEnd
.Stop();
569 // Stop our Inotify threads
572 // Shut down the global scheduler
573 Scheduler
.Global
.Stop ();
575 // Stop the messaging server
579 /////////////////////////////////////////////////////////////////////////////
582 private static ArrayList exercise_files
= new ArrayList ();
584 private static void ExerciseTheDogHarder ()
587 path
= Environment
.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG_HARDER");
591 DirectoryInfo dir
= new DirectoryInfo (path
);
592 foreach (FileInfo file
in dir
.GetFiles ())
593 exercise_files
.Add (file
);
594 if (exercise_files
.Count
== 0)
598 if (N
> exercise_files
.Count
)
599 N
= exercise_files
.Count
;
601 for (int i
= 0; i
< N
; ++i
)
602 ExceptionHandlingThread
.Start (new ThreadStart (ExerciseTheDogHarderWorker
));
605 private static void ExerciseTheDogHarderWorker ()
607 Random rng
= new Random ();
609 while (! Shutdown
.ShutdownRequested
) {
611 FileInfo file
= null;
615 lock (exercise_files
) {
617 i
= rng
.Next (exercise_files
.Count
);
618 file
= exercise_files
[i
] as FileInfo
;
619 } while (file
== null);
620 exercise_files
[i
] = null;
624 target
= Path
.Combine (PathFinder
.HomeDir
, "_HARDER_" + file
.Name
);
626 Logger
.Log
.Debug ("ETDH: Copying {0}", file
.Name
);
627 file
.CopyTo (target
, true);
629 lock (exercise_files
)
630 exercise_files
[i
] = file
;
632 Thread
.Sleep (500 + rng
.Next (500));