processhelp.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. #***************************************************************************
  2. # _ _ ____ _
  3. # Project ___| | | | _ \| |
  4. # / __| | | | |_) | |
  5. # | (__| |_| | _ <| |___
  6. # \___|\___/|_| \_\_____|
  7. #
  8. # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  9. #
  10. # This software is licensed as described in the file COPYING, which
  11. # you should have received as part of this distribution. The terms
  12. # are also available at https://curl.se/docs/copyright.html.
  13. #
  14. # You may opt to use, copy, modify, merge, publish, distribute and/or sell
  15. # copies of the Software, and permit persons to whom the Software is
  16. # furnished to do so, under the terms of the COPYING file.
  17. #
  18. # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  19. # KIND, either express or implied.
  20. #
  21. # SPDX-License-Identifier: curl
  22. #
  23. ###########################################################################
  24. package processhelp;
  25. use strict;
  26. use warnings;
  27. BEGIN {
  28. use base qw(Exporter);
  29. our @EXPORT = qw(
  30. portable_sleep
  31. pidfromfile
  32. pidexists
  33. pidwait
  34. processexists
  35. killpid
  36. killsockfilters
  37. killallsockfilters
  38. set_advisor_read_lock
  39. clear_advisor_read_lock
  40. );
  41. # portable sleeping needs Time::HiRes
  42. eval {
  43. no warnings "all";
  44. require Time::HiRes;
  45. };
  46. # portable sleeping falls back to native Sleep on Windows
  47. eval {
  48. no warnings "all";
  49. require Win32;
  50. }
  51. }
  52. use serverhelp qw(
  53. servername_id
  54. mainsockf_pidfilename
  55. datasockf_pidfilename
  56. );
  57. use pathhelp qw(
  58. os_is_win
  59. );
  60. #######################################################################
  61. # portable_sleep uses Time::HiRes::sleep if available and falls back
  62. # to the classic approach of using select(undef, undef, undef, ...).
  63. # even though that one is not portable due to being implemented using
  64. # select on Windows: https://perldoc.perl.org/perlport.html#select
  65. # Therefore it uses Win32::Sleep on Windows systems instead.
  66. #
  67. sub portable_sleep {
  68. my ($seconds) = @_;
  69. if($Time::HiRes::VERSION) {
  70. Time::HiRes::sleep($seconds);
  71. }
  72. elsif (os_is_win()) {
  73. Win32::Sleep($seconds*1000);
  74. }
  75. else {
  76. select(undef, undef, undef, $seconds);
  77. }
  78. }
  79. #######################################################################
  80. # pidfromfile returns the pid stored in the given pidfile. The value
  81. # of the returned pid will never be a negative value. It will be zero
  82. # on any file related error or if a pid can not be extracted from the
  83. # given file.
  84. #
  85. sub pidfromfile {
  86. my $pidfile = $_[0];
  87. my $pid = 0;
  88. if(-f $pidfile && -s $pidfile && open(my $pidfh, "<", "$pidfile")) {
  89. $pid = 0 + <$pidfh>;
  90. close($pidfh);
  91. $pid = 0 if($pid < 0);
  92. }
  93. return $pid;
  94. }
  95. #######################################################################
  96. # return Cygwin pid from virtual pid
  97. #
  98. sub winpid_to_pid {
  99. my $vpid = $_[0];
  100. if(($^O eq 'cygwin' || $^O eq 'msys') && $vpid > 65536) {
  101. my $pid = Cygwin::winpid_to_pid($vpid - 65536);
  102. if($pid) {
  103. return $pid;
  104. } else {
  105. return $vpid
  106. }
  107. }
  108. return $vpid;
  109. }
  110. #######################################################################
  111. # pidexists checks if a process with a given pid exists and is alive.
  112. # This will return the positive pid if the process exists and is alive.
  113. # This will return the negative pid if the process exists differently.
  114. # This will return 0 if the process could not be found.
  115. #
  116. sub pidexists {
  117. my $pid = $_[0];
  118. if($pid > 0) {
  119. # verify if currently existing Windows process
  120. $pid = winpid_to_pid($pid);
  121. if ($pid > 65536 && os_is_win()) {
  122. $pid -= 65536;
  123. if($^O ne 'MSWin32') {
  124. my $filter = "PID eq $pid";
  125. my $result = `tasklist -fi \"$filter\" 2>nul`;
  126. if(index($result, "$pid") != -1) {
  127. return -$pid;
  128. }
  129. return 0;
  130. }
  131. }
  132. # verify if currently existing and alive
  133. if(kill(0, $pid)) {
  134. return $pid;
  135. }
  136. }
  137. return 0;
  138. }
  139. #######################################################################
  140. # pidterm asks the process with a given pid to terminate gracefully.
  141. #
  142. sub pidterm {
  143. my $pid = $_[0];
  144. if($pid > 0) {
  145. # request the process to quit
  146. $pid = winpid_to_pid($pid);
  147. if ($pid > 65536 && os_is_win()) {
  148. $pid -= 65536;
  149. if($^O ne 'MSWin32') {
  150. my $filter = "PID eq $pid";
  151. my $result = `tasklist -fi \"$filter\" 2>nul`;
  152. if(index($result, "$pid") != -1) {
  153. system("taskkill -fi \"$filter\" >nul 2>&1");
  154. }
  155. return;
  156. }
  157. }
  158. # signal the process to terminate
  159. kill("TERM", $pid);
  160. }
  161. }
  162. #######################################################################
  163. # pidkill kills the process with a given pid mercilessly and forcefully.
  164. #
  165. sub pidkill {
  166. my $pid = $_[0];
  167. if($pid > 0) {
  168. # request the process to quit
  169. $pid = winpid_to_pid($pid);
  170. if ($pid > 65536 && os_is_win()) {
  171. $pid -= 65536;
  172. if($^O ne 'MSWin32') {
  173. my $filter = "PID eq $pid";
  174. my $result = `tasklist -fi \"$filter\" 2>nul`;
  175. if(index($result, "$pid") != -1) {
  176. system("taskkill -f -t -fi \"$filter\" >nul 2>&1");
  177. # Windows XP Home compatibility
  178. system("tskill $pid >nul 2>&1");
  179. }
  180. return;
  181. }
  182. }
  183. # signal the process to terminate
  184. kill("KILL", $pid);
  185. }
  186. }
  187. #######################################################################
  188. # pidwait waits for the process with a given pid to be terminated.
  189. #
  190. sub pidwait {
  191. my $pid = $_[0];
  192. my $flags = $_[1];
  193. $pid = winpid_to_pid($pid);
  194. # check if the process exists
  195. if ($pid > 65536 && os_is_win()) {
  196. if($flags == &WNOHANG) {
  197. return pidexists($pid)?0:$pid;
  198. }
  199. while(pidexists($pid)) {
  200. portable_sleep(0.01);
  201. }
  202. return $pid;
  203. }
  204. # wait on the process to terminate
  205. return waitpid($pid, $flags);
  206. }
  207. #######################################################################
  208. # processexists checks if a process with the pid stored in the given
  209. # pidfile exists and is alive. This will return 0 on any file related
  210. # error or if a pid can not be extracted from the given file. When a
  211. # process with the same pid as the one extracted from the given file
  212. # is currently alive this returns that positive pid. Otherwise, when
  213. # the process is not alive, will return the negative value of the pid.
  214. #
  215. sub processexists {
  216. use POSIX ":sys_wait_h";
  217. my $pidfile = $_[0];
  218. # fetch pid from pidfile
  219. my $pid = pidfromfile($pidfile);
  220. if($pid > 0) {
  221. # verify if currently alive
  222. if(pidexists($pid)) {
  223. return $pid;
  224. }
  225. else {
  226. # get rid of the certainly invalid pidfile
  227. unlink($pidfile) if($pid == pidfromfile($pidfile));
  228. # reap its dead children, if not done yet
  229. pidwait($pid, &WNOHANG);
  230. # negative return value means dead process
  231. return -$pid;
  232. }
  233. }
  234. return 0;
  235. }
  236. #######################################################################
  237. # killpid attempts to gracefully stop processes in the given pid list
  238. # with a SIGTERM signal and SIGKILLs those which haven't died on time.
  239. #
  240. sub killpid {
  241. my ($verbose, $pidlist) = @_;
  242. use POSIX ":sys_wait_h";
  243. my @requested;
  244. my @signalled;
  245. my @reapchild;
  246. # The 'pidlist' argument is a string of whitespace separated pids.
  247. return if(not defined($pidlist));
  248. # Make 'requested' hold the non-duplicate pids from 'pidlist'.
  249. @requested = split(' ', $pidlist);
  250. return if(not @requested);
  251. if(scalar(@requested) > 2) {
  252. @requested = sort({$a <=> $b} @requested);
  253. }
  254. for(my $i = scalar(@requested) - 2; $i >= 0; $i--) {
  255. if($requested[$i] == $requested[$i+1]) {
  256. splice @requested, $i+1, 1;
  257. }
  258. }
  259. # Send a SIGTERM to processes which are alive to gracefully stop them.
  260. foreach my $tmp (@requested) {
  261. chomp $tmp;
  262. if($tmp =~ /^(\d+)$/) {
  263. my $pid = $1;
  264. if($pid > 0) {
  265. if(pidexists($pid)) {
  266. print("RUN: Process with pid $pid signalled to die\n")
  267. if($verbose);
  268. pidterm($pid);
  269. push @signalled, $pid;
  270. }
  271. else {
  272. print("RUN: Process with pid $pid already dead\n")
  273. if($verbose);
  274. # if possible reap its dead children
  275. pidwait($pid, &WNOHANG);
  276. push @reapchild, $pid;
  277. }
  278. }
  279. }
  280. }
  281. # Allow all signalled processes five seconds to gracefully die.
  282. if(@signalled) {
  283. my $twentieths = 5 * 20;
  284. while($twentieths--) {
  285. for(my $i = scalar(@signalled) - 1; $i >= 0; $i--) {
  286. my $pid = $signalled[$i];
  287. if(!pidexists($pid)) {
  288. print("RUN: Process with pid $pid gracefully died\n")
  289. if($verbose);
  290. splice @signalled, $i, 1;
  291. # if possible reap its dead children
  292. pidwait($pid, &WNOHANG);
  293. push @reapchild, $pid;
  294. }
  295. }
  296. last if(not scalar(@signalled));
  297. portable_sleep(0.05);
  298. }
  299. }
  300. # Mercilessly SIGKILL processes still alive.
  301. if(@signalled) {
  302. foreach my $pid (@signalled) {
  303. if($pid > 0) {
  304. print("RUN: Process with pid $pid forced to die with SIGKILL\n")
  305. if($verbose);
  306. pidkill($pid);
  307. # if possible reap its dead children
  308. pidwait($pid, &WNOHANG);
  309. push @reapchild, $pid;
  310. }
  311. }
  312. }
  313. # Reap processes dead children for sure.
  314. if(@reapchild) {
  315. foreach my $pid (@reapchild) {
  316. if($pid > 0) {
  317. pidwait($pid, 0);
  318. }
  319. }
  320. }
  321. }
  322. #######################################################################
  323. # killsockfilters kills sockfilter processes for a given server.
  324. #
  325. sub killsockfilters {
  326. my ($piddir, $proto, $ipvnum, $idnum, $verbose, $which) = @_;
  327. my $server;
  328. my $pidfile;
  329. my $pid;
  330. return if($proto !~ /^(ftp|imap|pop3|smtp)$/);
  331. die "unsupported sockfilter: $which"
  332. if($which && ($which !~ /^(main|data)$/));
  333. $server = servername_id($proto, $ipvnum, $idnum) if($verbose);
  334. if(!$which || ($which eq 'main')) {
  335. $pidfile = mainsockf_pidfilename($piddir, $proto, $ipvnum, $idnum);
  336. $pid = processexists($pidfile);
  337. if($pid > 0) {
  338. printf("* kill pid for %s-%s => %d\n", $server,
  339. ($proto eq 'ftp')?'ctrl':'filt', $pid) if($verbose);
  340. pidkill($pid);
  341. pidwait($pid, 0);
  342. }
  343. unlink($pidfile) if(-f $pidfile);
  344. }
  345. return if($proto ne 'ftp');
  346. if(!$which || ($which eq 'data')) {
  347. $pidfile = datasockf_pidfilename($piddir, $proto, $ipvnum, $idnum);
  348. $pid = processexists($pidfile);
  349. if($pid > 0) {
  350. printf("* kill pid for %s-data => %d\n", $server,
  351. $pid) if($verbose);
  352. pidkill($pid);
  353. pidwait($pid, 0);
  354. }
  355. unlink($pidfile) if(-f $pidfile);
  356. }
  357. }
  358. #######################################################################
  359. # killallsockfilters kills sockfilter processes for all servers.
  360. #
  361. sub killallsockfilters {
  362. my ($piddir, $verbose) = @_;
  363. for my $proto (('ftp', 'imap', 'pop3', 'smtp')) {
  364. for my $ipvnum (('4', '6')) {
  365. for my $idnum (('1', '2')) {
  366. killsockfilters($piddir, $proto, $ipvnum, $idnum, $verbose);
  367. }
  368. }
  369. }
  370. }
  371. sub set_advisor_read_lock {
  372. my ($filename) = @_;
  373. my $fileh;
  374. if(open($fileh, ">", "$filename") && close($fileh)) {
  375. return;
  376. }
  377. printf "Error creating lock file $filename error: $!\n";
  378. }
  379. sub clear_advisor_read_lock {
  380. my ($filename) = @_;
  381. if(-f $filename) {
  382. unlink($filename);
  383. }
  384. }
  385. 1;