SubProcess.pm 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. # $Id: SubProcess.pm 14334 2017-05-20 23:11:06Z neubert $
  2. ##############################################################################
  3. #
  4. # SubProcess.pm
  5. # Copyright by Dr. Boris Neubert
  6. # e-mail: omega at online dot de
  7. #
  8. # This file is part of fhem.
  9. #
  10. # Fhem is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Fhem is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  22. #
  23. ##############################################################################
  24. package SubProcess;
  25. use warnings;
  26. use strict;
  27. use POSIX ":sys_wait_h";
  28. use Socket;
  29. use IO::Handle;
  30. #
  31. # creates a new subprocess
  32. #
  33. sub new() {
  34. my ($class, $args)= @_;
  35. my ($child, $parent);
  36. # http://perldoc.perl.org/functions/socketpair.html
  37. # man 2 socket
  38. # AF_UNIX Local communication
  39. # SOCK_STREAM Provides sequenced, reliable, two-way, connection-based
  40. # byte streams. An out-of-band data transmission mechanism
  41. # may be supported
  42. #
  43. socketpair($child, $parent, AF_UNIX, SOCK_STREAM || SOCK_NONBLOCK, PF_UNSPEC) ||
  44. return undef; # die "socketpair: $!";
  45. $child->autoflush(1);
  46. $parent->autoflush(1);
  47. # Buffers are not used in this version of SubProcess.pm
  48. # Revision 8393 had it
  49. my %childBuffer= ();
  50. my %parentBuffer= ();
  51. my $self= {
  52. onRun => $args->{onRun},
  53. onExit => $args->{onExit},
  54. timeout => $args->{timeout},
  55. timeoutread => $args->{timeoutread},
  56. timeoutwrite => $args->{timeoutwrite},
  57. child => $child,
  58. parent => $parent,
  59. pid => undef,
  60. childBufferRef => \%childBuffer,
  61. parentBufferRef => \%parentBuffer,
  62. lasterror => ''
  63. }; # we are a hash reference
  64. # Timeout must be defined and > 0
  65. # 0 = Polling, undef = Block until data available
  66. if(defined($self->{timeout})) {
  67. $self->{timeout} = 0.001 if ($self->{timeout} <= 0.0);
  68. }
  69. else {
  70. $self->{timeout} = 0.001;
  71. }
  72. if (!defined ($self->{timeoutread})) {
  73. $self->{timeoutread} = $self->{timeout};
  74. }
  75. if (!defined ($self->{timeoutwrite})) {
  76. $self->{timeoutwrite} = $self->{timeout};
  77. }
  78. return bless($self, $class); # make $self an object of class $class
  79. }
  80. sub lasterror() {
  81. my $self = shift;
  82. return exists ($self->{lasterror}) ? $self->{lasterror} : '';
  83. }
  84. #
  85. # returns the pid of the subprocess
  86. # undef if subprocess not available
  87. #
  88. sub pid() {
  89. my $self= shift;
  90. return $self->{pid};
  91. }
  92. #
  93. # return 1 if subprocess is still running, else 0
  94. #
  95. sub running() {
  96. my $self= shift;
  97. my $pid= $self->{pid};
  98. return waitpid($pid, WNOHANG) > 0 ? 1 : 0;
  99. }
  100. #
  101. # waits for the subprocess to terminate
  102. #
  103. sub wait() {
  104. my $self= shift;
  105. my $pid= $self->{pid};
  106. if(defined($pid)) {
  107. main::Log3 $pid, 5, "Waiting for SubProcess $pid...";
  108. waitpid($pid, 0);
  109. main::Log3 $pid, 5, "SubProcess $pid terminated.";
  110. }
  111. }
  112. #
  113. # send a POSIX signal to the subproess
  114. #
  115. sub signal() {
  116. my ($self, $signal)= @_;
  117. my $pid= $self->{pid};
  118. main::Log3 $pid, 5, "Sending signal $signal to SubProcess $pid...";
  119. return kill $signal, $pid;
  120. }
  121. #
  122. # terminates thr subprocess (HUP)
  123. #
  124. sub terminate() {
  125. my $self= shift;
  126. return $self->signal('HUP');
  127. }
  128. #
  129. # kills the subprocess (KILL)
  130. #
  131. sub kill() {
  132. my $self= shift;
  133. return $self->signal('KILL');
  134. }
  135. #
  136. # the socket used by the parent to communicate with the subprocess
  137. #
  138. sub child() {
  139. my $self= shift;
  140. return $self->{child};
  141. }
  142. #
  143. # the socket used by the subprocess to communicate with the parent
  144. #
  145. sub parent() {
  146. my $self= shift;
  147. return $self->{parent};
  148. }
  149. # this is a helper function for reading
  150. # returns 1 datagram or undef on error
  151. sub readFrom() {
  152. my ($self, $fh) = @_;
  153. my $header;
  154. my $data;
  155. # Check if data is available
  156. my $rin= '';
  157. vec($rin, fileno($fh), 1) = 1;
  158. my $nfound = select($rin, undef, undef, $self->{timeoutread});
  159. if ($nfound < 0) {
  160. $self->{lasterror} = $!;
  161. return undef;
  162. }
  163. elsif ($nfound == 0) {
  164. $self->{lasterror} = "read: no data";
  165. return undef;
  166. }
  167. # Read datagram size
  168. my $sbytes = sysread ($fh, $header, 4);
  169. if (!defined ($sbytes)) {
  170. $self->{lasterror} = $!;
  171. return undef;
  172. }
  173. elsif ($sbytes != 4) {
  174. $self->{lasterror} = "read: short header";
  175. return undef;
  176. }
  177. # Read datagram
  178. my $size = unpack ('N', $header);
  179. my $buffer;
  180. while($size> 0) {
  181. my $bytes = sysread ($fh, $buffer, $size);
  182. if (!defined ($bytes)) {
  183. $self->{lasterror} = $!;
  184. return undef;
  185. }
  186. $data.= $buffer;
  187. $size-= $bytes;
  188. }
  189. return $data;
  190. }
  191. # this is a helper function for writing
  192. # writes 4 byte datagram size + datagram
  193. sub writeTo() {
  194. my ($self, $fh, $msg) = @_;
  195. my $win= '';
  196. vec($win, fileno($fh), 1)= 1;
  197. my $nfound = select (undef, $win, undef, $self->{timeoutwrite});
  198. if ($nfound < 0) {
  199. $self->{lasterror} = $!;
  200. return undef;
  201. }
  202. elsif ($nfound == 0) {
  203. $self->{lasterror} = "write: no reader";
  204. return undef;
  205. }
  206. my $size= pack("N", length($msg));
  207. my $bytes= syswrite ($fh, $size . $msg);
  208. if (!defined ($bytes)) {
  209. $self->{lasterror} = $!;
  210. return undef;
  211. }
  212. elsif ($bytes != length ($size.$msg)) {
  213. $self->{lasterror} = "write: incomplete data";
  214. return undef;
  215. }
  216. return $bytes;
  217. }
  218. # this function is called from the parent to read from the subprocess
  219. # returns undef on error or if nothing was read
  220. sub readFromChild() {
  221. my $self= shift;
  222. return $self->readFrom($self->child());
  223. }
  224. # this function is called from the parent to write to the subprocess
  225. # returns 0 on error, else 1
  226. sub writeToChild() {
  227. my ($self, $msg)= @_;
  228. return $self->writeTo($self->child(), $msg);
  229. }
  230. # this function is called from the subprocess to read from the parent
  231. # returns undef on error or if nothing was read
  232. sub readFromParent() {
  233. my $self= shift;
  234. return $self->readFrom($self->parent());
  235. }
  236. # this function is called from the subprocess to write to the parent
  237. # returns 0 on error, else 1
  238. sub writeToParent() {
  239. my ($self, $msg)= @_;
  240. return $self->writeTo($self->parent(), $msg);
  241. }
  242. #
  243. # starts the subprocess
  244. #
  245. sub run() {
  246. my $self= shift;
  247. my $pid= fork;
  248. if(!defined($pid)) {
  249. main::Log3 undef, 2, "SubProcess: Cannot fork: $!";
  250. return undef;
  251. }
  252. $self->{pid}= $pid;
  253. if(!$pid) {
  254. # CHILD
  255. # run
  256. main::Log3 undef, 5, "SubProcess $$ started.";
  257. my $onRun= $self->{onRun};
  258. if(defined($onRun)) {
  259. eval { &$onRun($self) };
  260. main::Log3 undef, 2, "SubProcess: onRun returned error: $@" if($@);
  261. }
  262. # exit
  263. my $onExit= $self->{onExit};
  264. if(defined($onExit)) {
  265. eval { &$onExit($self) };
  266. main::Log3 undef, 2, "SubProcess: onExit returned error: $@" if($@);
  267. }
  268. main::Log3 undef, 5, "SubProcess $$ ended.";
  269. POSIX::_exit(0);
  270. } else {
  271. # PARENT
  272. main::Log3 $pid, 5, "SubProcess $pid created.";
  273. return $pid;
  274. }
  275. }
  276. 1;