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. logmsg
  57. );
  58. use pathhelp qw(
  59. os_is_win
  60. );
  61. #######################################################################
  62. # portable_sleep uses Time::HiRes::sleep if available and falls back
  63. # to the classic approach of using select(undef, undef, undef, ...).
  64. # even though that one is not portable due to being implemented using
  65. # select on Windows: https://perldoc.perl.org/perlport.html#select
  66. # Therefore it uses Win32::Sleep on Windows systems instead.
  67. #
  68. sub portable_sleep {
  69. my ($seconds) = @_;
  70. if($Time::HiRes::VERSION) {
  71. Time::HiRes::sleep($seconds);
  72. }
  73. elsif (os_is_win()) {
  74. Win32::Sleep($seconds*1000);
  75. }
  76. else {
  77. select(undef, undef, undef, $seconds);
  78. }
  79. }
  80. #######################################################################
  81. # pidfromfile returns the pid stored in the given pidfile. The value
  82. # of the returned pid will never be a negative value. It will be zero
  83. # on any file related error or if a pid can not be extracted from the
  84. # given file.
  85. #
  86. sub pidfromfile {
  87. my $pidfile = $_[0];
  88. my $pid = 0;
  89. if(-f $pidfile && -s $pidfile && open(my $pidfh, "<", "$pidfile")) {
  90. $pid = 0 + <$pidfh>;
  91. close($pidfh);
  92. $pid = 0 if($pid < 0);
  93. }
  94. return $pid;
  95. }
  96. #######################################################################
  97. # return Cygwin pid from virtual pid
  98. #
  99. sub winpid_to_pid {
  100. my $vpid = $_[0];
  101. if(($^O eq 'cygwin' || $^O eq 'msys') && $vpid > 65536) {
  102. my $pid = Cygwin::winpid_to_pid($vpid - 65536);
  103. if($pid) {
  104. return $pid;
  105. } else {
  106. return $vpid
  107. }
  108. }
  109. return $vpid;
  110. }
  111. #######################################################################
  112. # pidexists checks if a process with a given pid exists and is alive.
  113. # This will return the positive pid if the process exists and is alive.
  114. # This will return the negative pid if the process exists differently.
  115. # This will return 0 if the process could not be found.
  116. #
  117. sub pidexists {
  118. my $pid = $_[0];
  119. if($pid > 0) {
  120. # verify if currently existing Windows process
  121. $pid = winpid_to_pid($pid);
  122. if ($pid > 65536 && os_is_win()) {
  123. $pid -= 65536;
  124. if($^O ne 'MSWin32') {
  125. my $filter = "PID eq $pid";
  126. # https://ss64.com/nt/tasklist.html
  127. my $result = `tasklist -fi \"$filter\" 2>nul`;
  128. if(index($result, "$pid") != -1) {
  129. return -$pid;
  130. }
  131. return 0;
  132. }
  133. }
  134. # verify if currently existing and alive
  135. if(kill(0, $pid)) {
  136. return $pid;
  137. }
  138. }
  139. return 0;
  140. }
  141. #######################################################################
  142. # pidterm asks the process with a given pid to terminate gracefully.
  143. #
  144. sub pidterm {
  145. my $pid = $_[0];
  146. if($pid > 0) {
  147. # request the process to quit
  148. $pid = winpid_to_pid($pid);
  149. if ($pid > 65536 && os_is_win()) {
  150. $pid -= 65536;
  151. if($^O ne 'MSWin32') {
  152. # https://ss64.com/nt/taskkill.html
  153. my $cmd = "taskkill -t -pid $pid >nul 2>&1";
  154. logmsg "Executing: '$cmd'\n";
  155. system($cmd);
  156. return;
  157. }
  158. }
  159. # signal the process to terminate
  160. kill("TERM", $pid);
  161. }
  162. }
  163. #######################################################################
  164. # pidkill kills the process with a given pid mercilessly and forcefully.
  165. #
  166. sub pidkill {
  167. my $pid = $_[0];
  168. if($pid > 0) {
  169. # request the process to quit
  170. $pid = winpid_to_pid($pid);
  171. if ($pid > 65536 && os_is_win()) {
  172. $pid -= 65536;
  173. if($^O ne 'MSWin32') {
  174. # https://ss64.com/nt/taskkill.html
  175. my $cmd = "taskkill -f -t -pid $pid >nul 2>&1";
  176. logmsg "Executing: '$cmd'\n";
  177. system($cmd);
  178. return;
  179. }
  180. }
  181. # signal the process to terminate
  182. kill("KILL", $pid);
  183. }
  184. }
  185. #######################################################################
  186. # pidwait waits for the process with a given pid to be terminated.
  187. #
  188. sub pidwait {
  189. my $pid = $_[0];
  190. my $flags = $_[1];
  191. $pid = winpid_to_pid($pid);
  192. # check if the process exists
  193. if ($pid > 65536 && os_is_win()) {
  194. if($flags == &WNOHANG) {
  195. return pidexists($pid)?0:$pid;
  196. }
  197. while(pidexists($pid)) {
  198. portable_sleep(0.01);
  199. }
  200. return $pid;
  201. }
  202. # wait on the process to terminate
  203. return waitpid($pid, $flags);
  204. }
  205. #######################################################################
  206. # processexists checks if a process with the pid stored in the given
  207. # pidfile exists and is alive. This will return 0 on any file related
  208. # error or if a pid can not be extracted from the given file. When a
  209. # process with the same pid as the one extracted from the given file
  210. # is currently alive this returns that positive pid. Otherwise, when
  211. # the process is not alive, will return the negative value of the pid.
  212. #
  213. sub processexists {
  214. use POSIX ":sys_wait_h";
  215. my $pidfile = $_[0];
  216. # fetch pid from pidfile
  217. my $pid = pidfromfile($pidfile);
  218. if($pid > 0) {
  219. # verify if currently alive
  220. if(pidexists($pid)) {
  221. return $pid;
  222. }
  223. else {
  224. # get rid of the certainly invalid pidfile
  225. unlink($pidfile) if($pid == pidfromfile($pidfile));
  226. # reap its dead children, if not done yet
  227. pidwait($pid, &WNOHANG);
  228. # negative return value means dead process
  229. return -$pid;
  230. }
  231. }
  232. return 0;
  233. }
  234. #######################################################################
  235. # killpid attempts to gracefully stop processes in the given pid list
  236. # with a SIGTERM signal and SIGKILLs those which haven't died on time.
  237. #
  238. sub killpid {
  239. my ($verbose, $pidlist) = @_;
  240. use POSIX ":sys_wait_h";
  241. my @requested;
  242. my @signalled;
  243. my @reapchild;
  244. # The 'pidlist' argument is a string of whitespace separated pids.
  245. return if(not defined($pidlist));
  246. # Make 'requested' hold the non-duplicate pids from 'pidlist'.
  247. @requested = split(' ', $pidlist);
  248. return if(not @requested);
  249. if(scalar(@requested) > 2) {
  250. @requested = sort({$a <=> $b} @requested);
  251. }
  252. for(my $i = scalar(@requested) - 2; $i >= 0; $i--) {
  253. if($requested[$i] == $requested[$i+1]) {
  254. splice @requested, $i+1, 1;
  255. }
  256. }
  257. # Send a SIGTERM to processes which are alive to gracefully stop them.
  258. foreach my $tmp (@requested) {
  259. chomp $tmp;
  260. if($tmp =~ /^(\d+)$/) {
  261. my $pid = $1;
  262. if($pid > 0) {
  263. if(pidexists($pid)) {
  264. print("RUN: Process with pid $pid signalled to die\n")
  265. if($verbose);
  266. pidterm($pid);
  267. push @signalled, $pid;
  268. }
  269. else {
  270. print("RUN: Process with pid $pid already dead\n")
  271. if($verbose);
  272. # if possible reap its dead children
  273. pidwait($pid, &WNOHANG);
  274. push @reapchild, $pid;
  275. }
  276. }
  277. }
  278. }
  279. # Allow all signalled processes five seconds to gracefully die.
  280. if(@signalled) {
  281. my $twentieths = 5 * 20;
  282. while($twentieths--) {
  283. for(my $i = scalar(@signalled) - 1; $i >= 0; $i--) {
  284. my $pid = $signalled[$i];
  285. if(!pidexists($pid)) {
  286. print("RUN: Process with pid $pid gracefully died\n")
  287. if($verbose);
  288. splice @signalled, $i, 1;
  289. # if possible reap its dead children
  290. pidwait($pid, &WNOHANG);
  291. push @reapchild, $pid;
  292. }
  293. }
  294. last if(not scalar(@signalled));
  295. # give any zombies of us a chance to move on to the afterlife
  296. pidwait(0, &WNOHANG);
  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;