ridelogic_hellod 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. #!/usr/bin/perl -Tw
  2. #
  3. # Copyright (c) 2019 Clementine Computing LLC.
  4. #
  5. # This file is part of PopuFare.
  6. #
  7. # PopuFare is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # PopuFare is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with PopuFare. If not, see <https://www.gnu.org/licenses/>.
  19. #
  20. require 5.002;
  21. use strict;
  22. use Socket;
  23. use Carp;
  24. use FileHandle;
  25. use Fcntl;
  26. use Getopt::Long qw(:config no_ignore_case);
  27. use POSIX;
  28. use RideLogic;
  29. my $bind_ip = '127.0.0.1';
  30. my $bind_port = 3556;
  31. my $DebugMode = 0;
  32. # This function only executes the passed code reference if the global variable $DebugMode is non-zero.
  33. # The reason for this is that any calculation (like a FooBar::ComplexObject->toString call) will not be
  34. # performed if we are not in debug mode, sort of like a very limited form of lazy evaluation.
  35. #
  36. sub ifdebug(&@)
  37. {
  38. my ($cmd) = @_;
  39. &$cmd() if($DebugMode);
  40. }
  41. sub handle_client()
  42. {
  43. close SERVER;
  44. CLIENT->autoflush(1);
  45. print CLIENT 'Hello.';
  46. close CLIENT;
  47. }
  48. my $waitedpid = 0;
  49. my $sigreceived = 0;
  50. sub REAPER
  51. {
  52. while (($waitedpid = waitpid(-1, WNOHANG))>0) { }
  53. $SIG{CHLD} = \&REAPER; # loathe sysV
  54. $sigreceived = 1;
  55. }
  56. sub spawn
  57. {
  58. my $coderef = shift; #grab the first parameter
  59. unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') #verify that it consists of a non-null block of executable perl code
  60. {
  61. confess "usage: spawn CODEREF"; #complain if this is not the case
  62. }
  63. my $pid;
  64. if (!defined($pid = fork)) #attempt a fork, remembering the returned PID value
  65. {
  66. close CLIENT;
  67. return; #failed to fork, we'd better close the client
  68. }
  69. elsif ($pid) #If the returned process ID is non-zero, that indicates that we are the parent process
  70. {
  71. return; # i'm the parent
  72. }
  73. else #otherwise, if the returned process ID is 0, that means we're the child process
  74. {
  75. exit &$coderef(); #in which case, we want to execute the child handler that was passed in, and then
  76. #exit this (child) process when we've finished our conversation(s) with the
  77. #other (client) end of the socket.
  78. }
  79. }
  80. sub show_help_and_exit {
  81. print "usage:\n";
  82. print " [-i] interactive, do not daemonize\n";
  83. print " [-c cfg] use cfg as config file (default to " . $RideLogic::RIDELOGIC_DAEMON_CONF . ") \n";
  84. print " [-h] show help (this screen)\n";
  85. exit;
  86. }
  87. #----------------------------------------------------------------------
  88. #
  89. #----------------------------------------------------------------------
  90. my $daemonize = 1;
  91. my $interactive = 0;
  92. my $show_help = 0;
  93. my $cfg_file = $RideLogic::RIDELOGIC_DAEMON_CONF;
  94. GetOptions(
  95. 'i|interactive' => \$interactive,
  96. 'c|config=s' => \$cfg_file,
  97. 'h|help' => \$show_help );
  98. show_help_and_exit() if ($show_help);
  99. $daemonize=0 if ($interactive);
  100. #----------------------------------------------------------------------
  101. # Local network settings for Inter-Process communication.
  102. #----------------------------------------------------------------------
  103. my $proto = getprotobyname('tcp');
  104. my $addr = sockaddr_in( $bind_port ,inet_aton($bind_ip));;
  105. #----------------------------------------------------------------------
  106. my $max_retries = 10; #Maximum number of address-binding retries before we give up.
  107. my $retry_count = $max_retries; #number of retries left...
  108. my $retry_delay = 3; #number of seconds to wait between retries at binding to our designated IPC address
  109. my $got_network = 0; #flag to let us know that we can quit retrying once we have gotten a valid listening socket
  110. my %CFG_VAR;
  111. read_config($cfg_file, \%CFG_VAR) if ($cfg_file);
  112. my $logfile = ($CFG_VAR{"RIDELOGIC_DAEMON_LOG_DIR"} || $RideLogic::RIDELOGIC_DAEMON_LOG_DIR) . "/ridelogic_hellod.log";
  113. my $pidfile = ($CFG_VAR{"RIDELOGIC_DAEMON_PID_DIR"} || $RideLogic::RIDELOGIC_DAEMON_PID_DIR) . "/ridelogic_hellod.pid";
  114. daemonize($logfile, $pidfile) if ($daemonize);
  115. # set our pipes to be piping hot
  116. $|=1;
  117. while( ($retry_count > 0) && (!$got_network) )
  118. {
  119. try #Try and allocate a socket, bind it to our IPC address, and set it to listen for connections
  120. {
  121. socket(SERVER,PF_INET,SOCK_STREAM,$proto) || die "socket: $!";
  122. setsockopt(SERVER, SOL_SOCKET, SO_REUSEADDR, 1);
  123. bind (SERVER, $addr) || die "bind: $!";
  124. listen(SERVER,5) || die "listen: $!";
  125. $got_network = 1;
  126. }
  127. catch #If that didn't work for some reason, log the error, clean up, and prepair to retry
  128. {
  129. my $errmsg = $_; #Remember the error message
  130. close(SERVER); #Clean up the server socket if it needs it
  131. #Decrement our remaining retry counter
  132. $retry_count = $retry_count - 1;
  133. #Log the message to our debug log
  134. print "Failed to allocate socket, will retry $retry_count times: $errmsg\n";
  135. #Wait a reasonable period before trying again
  136. sleep $retry_delay;
  137. };
  138. }
  139. if($got_network) #If we met with success binding to the network, report it
  140. {
  141. my $logmsg = "Socket setup successful. Listening for clients at $bind_ip:$bind_port\n";
  142. print $logmsg;
  143. }
  144. else #If we ran out of patience and gave up, report that as well and exit
  145. {
  146. my $errmsg = "Could not allocate and bind listening socket at $bind_ip:$bind_port after $max_retries attempts.\n";
  147. die $errmsg;
  148. }
  149. # Set up our signal handler which will clean up defunct child processes and let the main
  150. # accept() loop know that the reason accept returned was due to a signal, not a legit connection.
  151. $SIG{CHLD} = \&REAPER;
  152. #This for loop is efficient, but confusting, so I'll break it down by clause
  153. #
  154. # The first clause ($sigreceived = 0) clears the signal received flag that will be set if the
  155. # accept() call was interrupted by a signal. This clause runs once before the first run of the loop
  156. #
  157. # The second clause is the test clause, it will process the contents of the loop if EITHER
  158. # accept() has returned (presumably generating a valid file handle for the CLIENT end of the
  159. # socket, OR the signal received flag is set (thus accept would have returned early without
  160. # having actually accepted a connection.
  161. #
  162. # The third clause (the 'incrementer') is run after each time the body is executed, before the
  163. # test clause is executed again (deciding whether to run the body or drop out... This test
  164. # clause will close the parent process' copy of the CLIENT file handle since (see body below)
  165. # after the body executes, all communication with the socket referred to by that file handle
  166. # will be carried out by the spawned child process. This frees the parent's copy of the CLIENT
  167. # file handle to be used again in the parent process for the next accepted incoming connection.
  168. for ( $sigreceived = 0; accept(CLIENT,SERVER) || $sigreceived; $sigreceived = 0, close CLIENT)
  169. {
  170. next if $sigreceived; #If we were interrupted by a signal, there is no real client, just go back and try to accept a new one
  171. print "connection received.\n"; #Print a diagnostic message confirming that we have made a connection
  172. spawn sub {handle_client();}; #fork() off a child process that will handle communication with the socket pointed to by the CLIENT file handle
  173. }