PageRenderTime 28ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Effect/Daemon.pm

https://github.com/jhuckaby/Effect-Games
Perl | 447 lines | 274 code | 72 blank | 101 comment | 48 complexity | 6c3936fc0be85b622b2bf1be17c16b45 MD5 | raw file
  1. package Effect::Daemon;
  2. # Effect Games Engine and IDE v1.0
  3. # Copyright (c) 2005 - 2011 Joseph Huckaby
  4. # Source Code released under the MIT License:
  5. # http://www.opensource.org/licenses/mit-license.php
  6. ##
  7. # Generic Preforking HTTP Server
  8. ##
  9. use strict;
  10. no strict 'refs';
  11. use English qw( -no_match_vars ) ;
  12. use FileHandle;
  13. use File::Basename;
  14. use File::Path;
  15. use Time::HiRes qw/time sleep/;
  16. use Time::Local qw/timelocal timelocal_nocheck/;
  17. use Digest::MD5 qw/md5 md5_hex/;
  18. use URI::Escape;
  19. use HTTP::Daemon;
  20. use HTTP::Request;
  21. use HTTP::Response;
  22. use Carp ();
  23. use POSIX qw/:sys_wait_h setsid/;
  24. use UNIVERSAL qw/isa/;
  25. $| = 1;
  26. if ($UID != 0) { die "\nError: Must be root to use this. Exiting.\n"; }
  27. sub new {
  28. ##
  29. # Class constructor
  30. ##
  31. my $class = shift;
  32. my $self = bless( {@_}, $class );
  33. if (!$self->{request_handler}) { die "Must pass request_handler to daemon constructor."; }
  34. $self->{name} ||= 'Generic Server';
  35. $self->{process_name} ||= 'GenericServer';
  36. $self->{pid_file} ||= '/var/run/generic_server.pid';
  37. $self->{debug_level} ||= 1;
  38. $self->{port} ||= 8080;
  39. $self->{max_children} ||= 1;
  40. $self->{max_requests_per_child} ||= 0;
  41. $self->{growl} ||= '';
  42. return $self;
  43. }
  44. sub startup {
  45. ##
  46. # Become daemon, setup signal handling and start socket listener
  47. ##
  48. my $self = shift;
  49. my $daemon_pid = $self->become_daemon();
  50. $self->{daemon_pid} = $daemon_pid;
  51. $self->update_daemon_status( 'Startup' );
  52. $self->log_debug(1, $self->{name} . " starting up");
  53. ##
  54. # Install signal handlers to catch warnings and crashes
  55. ##
  56. $SIG{'__WARN__'} = sub {
  57. my ($package_name, undef, undef) = caller();
  58. $self->log_debug( 4, $_[0] );
  59. };
  60. $SIG{'__DIE__'} = sub {
  61. # my ($package_name, undef, undef) = caller();
  62. # $self->log_debug( 1, "Fatal Error: " . $_[0] );
  63. Carp::cluck("Stack Trace");
  64. };
  65. ##
  66. # Keep track of child processes
  67. ##
  68. $self->{zombies} = 0;
  69. $self->{active_kids} = {};
  70. ##
  71. # Install signal handlers
  72. ##
  73. $self->install_signal_handlers();
  74. ##
  75. # Write daemon's PID file
  76. ##
  77. $self->write_pid_file();
  78. if ($self->{user}) { $self->become_web_user(); }
  79. ##
  80. # Start server
  81. ##
  82. $self->log_debug( 1, "Starting socket listener on port " . $self->{port} );
  83. $self->{server} = HTTP::Daemon->new(
  84. LocalPort => $self->{port},
  85. # LocalAddr => '127.0.0.1',
  86. Reuse => 1,
  87. Timeout => 86400
  88. ) || die "Cannot create socket: $!\n";
  89. }
  90. sub idle {
  91. ##
  92. # Manage children
  93. ##
  94. my $self = shift;
  95. $self->update_daemon_status( 'Active' );
  96. $self->log_debug( 1, "Daemon resuming normal operations." );
  97. while (1) {
  98. my $num_children = scalar keys %{$self->{active_kids}};
  99. while ($num_children < $self->{max_children}) {
  100. $self->spawn_child();
  101. $num_children++;
  102. }
  103. if ($self->{idle_handler}) {
  104. $self->{idle_handler}->( $self );
  105. }
  106. $self->reaper() if $self->{zombies};
  107. last if $self->{sig_term};
  108. sleep 1;
  109. } # infinite loop
  110. $self->log_debug( 1, "Shutting down" );
  111. $self->kill_all_children();
  112. unlink $self->{pid_file};
  113. $self->log_debug( 1, $self->{name} . " exiting");
  114. }
  115. sub spawn_custom {
  116. ##
  117. # Spawn child to perform custom task (pass in func ref)
  118. ##
  119. my ($self, $func) = @_;
  120. $self->log_debug( 2, "Forking new custom child");
  121. my $pid = fork();
  122. if (defined($pid)) {
  123. ##
  124. # Fork was successful
  125. ##
  126. if ($pid) {
  127. ##
  128. # Parent division of fork
  129. ##
  130. $self->{active_kids}->{$pid} = 1;
  131. $self->log_debug( 2, "Forked child (PID: " . $pid . ")" );
  132. $self->update_daemon_status( 'Active' );
  133. return $pid;
  134. }
  135. else {
  136. ##
  137. # Child division of fork
  138. ##
  139. $self->log_debug( 2, "Child starting up");
  140. $self->set_process_status( 'Child' );
  141. $func->( $self );
  142. $self->log_debug( 2, "Child exiting (custom)");
  143. exit();
  144. }
  145. }
  146. else {
  147. die "Could not fork: $!\n";
  148. }
  149. }
  150. sub spawn_child {
  151. ##
  152. # Spawn new child
  153. ##
  154. my $self = shift;
  155. $self->log_debug( 2, "Forking new child");
  156. my $pid = fork();
  157. if (defined($pid)) {
  158. ##
  159. # Fork was successful
  160. ##
  161. if ($pid) {
  162. ##
  163. # Parent division of fork
  164. ##
  165. $self->{active_kids}->{$pid} = 1;
  166. $self->log_debug( 2, "Forked child (PID: " . $pid . ")" );
  167. $self->update_daemon_status( 'Active' );
  168. }
  169. else {
  170. ##
  171. # Child division of fork
  172. ##
  173. $self->log_debug( 2, "Child starting up");
  174. my $max_reqs = $self->{max_requests_per_child};
  175. my $req_num = 0;
  176. $self->set_process_status( 'Child' );
  177. while (($req_num < $max_reqs) || !$max_reqs) {
  178. $req_num++;
  179. my $c = $self->{server}->accept() or last;
  180. $c->autoflush(1);
  181. $self->log_debug(3, "New connection from: " . $c->peerhost() );
  182. # Get the request
  183. my $r = $c->get_request() or last;
  184. my $uri = $r->url();
  185. $self->log_debug( 4, "Request URI: $uri" );
  186. $self->{socket} = $c;
  187. eval {
  188. $self->{request_handler}->( $self, $r, $c );
  189. };
  190. if ($self->{cleanup_handler}) {
  191. # always call cleanup handler, regardless
  192. $self->{cleanup_handler}->( $self, $r, $c );
  193. }
  194. if ($@) {
  195. # handler crashed, send back HTTP 500
  196. $self->log_debug(1, "HTTP 500 Internal Server Error: $@");
  197. my $response = HTTP::Response->new( 500, "Internal Server Error" );
  198. $response->content("Internal Server Error: $@");
  199. $response->header("Content-Type" => "text/html");
  200. $c->send_response($response);
  201. $c->close();
  202. }
  203. $self->log_debug(3, "Request end");
  204. } # child request loop
  205. $self->log_debug( 2, "Child exiting ($req_num total requests)");
  206. exit();
  207. }
  208. }
  209. else {
  210. die "Could not fork: $!\n";
  211. }
  212. }
  213. sub send_response {
  214. ##
  215. # Send custom HTTP response
  216. ##
  217. my ($self, $code, $msg, $content) = @_;
  218. $self->log_debug(4, "HTTP $code $msg");
  219. my $response = HTTP::Response->new( $code, $msg );
  220. if ($content) {
  221. $response->header("Content-Type" => "text/html");
  222. $response->content( $content );
  223. }
  224. $self->{socket}->send_response($response);
  225. $self->{socket}->close();
  226. return 1;
  227. }
  228. sub become_web_user {
  229. ##
  230. # Become web user
  231. ##
  232. my $self = shift;
  233. my (undef, undef, $n_uid, $n_gid) = getpwnam( $self->{user} );
  234. if (!$n_uid) { die "Cannot determine web UID for: " . $self->{user}; }
  235. if ($EUID != $n_uid) {
  236. # print "Becoming web user...";
  237. $GID = $EGID = $n_gid;
  238. $UID = $EUID = $n_uid;
  239. # print "done.\n";
  240. }
  241. }
  242. sub install_signal_handlers {
  243. ##
  244. # Install handler functions for common signals.
  245. ##
  246. my $self = shift;
  247. $SIG{CHLD} = sub { $self->{zombies}++; };
  248. $SIG{TERM} = sub { $self->{sig_term} = 1; };
  249. }
  250. sub kill_all_children {
  251. ##
  252. # Send SIGTERM to all active children
  253. ##
  254. my $self = shift;
  255. foreach my $kid (keys %{$self->{active_kids}}) {
  256. $self->log_debug( 1, "Killing child: $kid");
  257. kill( 1, $kid ); # SIGTERM
  258. }
  259. }
  260. sub update_daemon_status {
  261. ##
  262. # Update daemon status in OS process table.
  263. ##
  264. my ($self, $mode) = @_;
  265. my $total_kids = scalar keys %{$self->{active_kids}};
  266. if ($total_kids > 1) {
  267. $self->set_process_status( "Daemon: $total_kids kids" );
  268. }
  269. elsif ($total_kids == 1) {
  270. $self->set_process_status( "Daemon: 1 kid" );
  271. }
  272. else {
  273. $self->set_process_status( "Daemon: $mode" );
  274. }
  275. }
  276. sub set_process_status {
  277. ##
  278. # Set daemon status in OS process table. This string shows up in
  279. # `ps -ef` calls on Linux, or `ps -aux` calls on MacOS X.
  280. ##
  281. my ($self, $msg) = @_;
  282. $0 = $self->{process_name} . " " . $msg;
  283. }
  284. sub reaper {
  285. ##
  286. # Reap child zombies -- compile hash of child exit status codes
  287. ##
  288. my $self = shift;
  289. $self->{zombies} = 0;
  290. foreach my $pid (keys %{$self->{active_kids}}) {
  291. if ((my $zombie = waitpid($pid, WNOHANG)) > 0) {
  292. ##
  293. # Check if child exited cleanly
  294. ##
  295. my $child_exit_code = $?;
  296. if ($child_exit_code) {
  297. ##
  298. # Non-zero exit code means something bad happened.
  299. ##
  300. $self->log_debug( 1, "Child (PID: $zombie) exited improperly with code: $child_exit_code" );
  301. }
  302. else {
  303. $self->log_debug( 2, "Child (PID: $zombie) exited cleanly" );
  304. }
  305. ##
  306. # Clear child PID from tracking hash
  307. ##
  308. delete $self->{active_kids}->{$zombie};
  309. $self->update_daemon_status( 'Active' );
  310. }
  311. }
  312. }
  313. sub write_pid_file {
  314. ##
  315. # Check for running daemon, and write PID file
  316. ##
  317. my $self = shift;
  318. if (defined($self->{pid_file}) && $self->{pid_file}) {
  319. if (-e $self->{pid_file}) {
  320. my $fh = new FileHandle("<" . $self->{pid_file});
  321. if ($fh) {
  322. my $old_pid = <$fh>;
  323. undef $fh;
  324. chomp $old_pid;
  325. if (kill(0, $old_pid)) {
  326. open( STDERR, ">/dev/tty" );
  327. die "Error: Daemon already running at PID: $old_pid.\n";
  328. }
  329. }
  330. }
  331. my $fh = new FileHandle(">" . $self->{pid_file});
  332. if ($fh) {
  333. $fh->print($$."\n");
  334. $fh->close();
  335. }
  336. else {
  337. $self->log_debug( 1, "Could not create PID file: $self->{pid_file}: $!" );
  338. }
  339. }
  340. }
  341. sub become_daemon {
  342. ##
  343. # Fork daemon process and disassociate from terminal
  344. ##
  345. my $self = shift;
  346. my $pid = fork();
  347. if (!defined($pid)) { die "Error: Cannot fork daemon process: $!\n"; }
  348. if ($pid) { exit(0); }
  349. setsid();
  350. open( STDIN, "</dev/null" );
  351. open( STDOUT, ">/dev/null" );
  352. chdir( '/' );
  353. umask( 0 );
  354. return $$;
  355. }
  356. sub log_debug {
  357. my ($self, $level, $msg) = @_;
  358. if ($self->{logger} && ($level <= $self->{debug_level})) {
  359. $self->{logger}->log_print(
  360. log => 'debug',
  361. component => ($$ == $self->{daemon_pid}) ? 'daemon' : 'child',
  362. code => $level,
  363. msg => $msg
  364. );
  365. if (($level == 1) && ($self->{growl})) {
  366. # $self->{logger}->log_print( log=>'debug', component=>'', code=>'2', msg=>"Opening pipe to growl: " . $self->{growl} );
  367. my $fh = FileHandle->new( "|" . $self->{growl} );
  368. if ($fh) {
  369. $fh->print( "$msg\n" );
  370. $fh->close();
  371. }
  372. }
  373. }
  374. }
  375. 1;