PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Utils/AppLauncher.pm

https://gitlab.com/aukkwat/CZPodzone-OpenKore-Bot
Perl | 331 lines | 163 code | 40 blank | 128 comment | 28 complexity | ce2e3c080205d0bfebc388951d47583c MD5 | raw file
  1. #########################################################################
  2. # OpenKore - Generic utility functions
  3. #
  4. # Copyright (c) 2006 OpenKore Development Team
  5. #
  6. # This software is open source, licensed under the GNU General Public
  7. # License, version 2.
  8. # Basically, this means that you're allowed to modify and distribute
  9. # this software. However, if you distribute modified versions, you MUST
  10. # also distribute the source code.
  11. # See http://www.gnu.org/licenses/gpl.html for the full license.
  12. #########################################################################
  13. ##
  14. # MODULE DESCRIPTION: Abstraction layer for launching applications
  15. #
  16. # The <code>AppLauncher</code> class provides a cross-platform way to
  17. # launch external applications.
  18. #
  19. # <b>See also:</b> @CLASS(PerlLauncher)
  20. #
  21. # <h3>Example</h3>
  22. # <pre class="example">
  23. # use Utils::AppLauncher;
  24. #
  25. # my $launcher = new AppLauncher('gedit', '/dev/null');
  26. # if (!$launcher->launch(0)) {
  27. # die "Cannot launch application.\n" .
  28. # "Error message: " . $launcher->getError() . "\n" .
  29. # "Error code: " . $launcher->getErrorCode() . "\n";
  30. # }
  31. #
  32. # while (1) {
  33. # if ($launcher->check()) {
  34. # print "App is still running.\n";
  35. # } else {
  36. # print "App has exited.\n";
  37. # print "Its exit code was: " . $launcher->getExitCode() . "\n";
  38. # last;
  39. # }
  40. # sleep 5;
  41. # }
  42. # </pre>
  43. package AppLauncher;
  44. use strict;
  45. ### CATEGORY: Class AppLauncher
  46. ##
  47. # AppLauncher AppLauncher->new(String app, [String arg...])
  48. # app: The application you want to run.
  49. # arg: The arguments you want to pass to the executable.
  50. # Ensures: !$self->isLaunched()
  51. #
  52. # Create a new AppLauncher object. The specified application
  53. # isn't run until you call $AppLauncher->launch()
  54. sub new {
  55. my $class = shift;
  56. my %self = (
  57. args => \@_,
  58. launched => 0
  59. );
  60. return bless \%self, $class;
  61. }
  62. ##
  63. # boolean $AppLauncher->launch(boolean detach)
  64. # detach: Set to 1 if you don't care when this application exists.
  65. # Returns: whether the application was successfully launched.
  66. # Ensures: $self->isLaunched() == result
  67. #
  68. # Launch the application asynchronously. That is, it will
  69. # not wait until the application has exited.
  70. #
  71. # If $detach is false, then you must periodically call
  72. # $AppLauncher->check() until it returns true. This is
  73. # to avoid zombie processes on Unix.
  74. #
  75. # If the launch failed, then you can use $AppLauncher->getError()
  76. # and $AppLauncher->getErrorCode() to get detailed information
  77. # about the cause.
  78. #
  79. # You must not call this function more than once. If this
  80. # function failed, and you want to try launching again, then you
  81. # must discard this object and create a new one.
  82. sub launch {
  83. $_[0]->{detached} = $_[1];
  84. if ($^O eq 'MSWin32') {
  85. &_launchWin32;
  86. } else {
  87. &_launchUnix;
  88. }
  89. }
  90. ##
  91. # boolean $AppLauncher->isLaunched()
  92. #
  93. # Check whether $AppLauncher->launch() had successfully launched
  94. # the application.
  95. sub isLaunched {
  96. return $_[0]->{launched};
  97. }
  98. ##
  99. # boolean $AppLauncher->isDetached()
  100. # Requires: $self->isLaunched()
  101. #
  102. # Check whether the application was launched in detached mode.
  103. # That is, whether $AppLauncher->launch() was called with the detach
  104. # parameter set to true.
  105. sub isDetached {
  106. return $_[0]->{detached};
  107. }
  108. ##
  109. # boolean $AppLauncher->check()
  110. # Requires: $self->isLaunched() && !$self->isDetached()
  111. #
  112. # Check whether the launched application is still running.
  113. #
  114. # If the application has exited (that is, result is false), then you
  115. # can use $AppLauncher->getExitCode() to retrieve the application's
  116. # exit code.
  117. #
  118. # You should periodically call this function. On Unix, not calling
  119. # this function can lead to zombie processes.
  120. sub check {
  121. if (exists $_[0]->{exitCode}) {
  122. return 0;
  123. } elsif ($^O eq 'MSWin32') {
  124. &_checkWin32;
  125. } else {
  126. &_checkUnix;
  127. }
  128. }
  129. ##
  130. # boolean $AppLauncher->getExitCode()
  131. # Requires: !$self->check()
  132. #
  133. # Retrieve the launched application's exit code. The application
  134. # must have exited.
  135. sub getExitCode {
  136. return $_[0]->{exitCode};
  137. }
  138. ##
  139. # $AppLauncher->getPID()
  140. # Requires: $self->isLaunched()
  141. # Ensures: defined(result)
  142. #
  143. # Returns the launched application's PID (on Unix), or its
  144. # Win32::Process object (on Windows).
  145. sub getPID {
  146. return $_[0]->{pid};
  147. }
  148. ##
  149. # String $AppLauncher->getError()
  150. sub getError {
  151. return $_[0]->{error};
  152. }
  153. ##
  154. # int $AppLauncher->getErrorCode()
  155. sub getErrorCode {
  156. return $_[0]->{errno};
  157. }
  158. ################## Win32 implementation ##################
  159. sub _launchWin32 {
  160. my ($self, $detach) = @_;
  161. my ($app, @args, $priority, $obj);
  162. @args = @{$self->{args}};
  163. $app = $args[0];
  164. foreach my $arg (@args) {
  165. $arg = '"' . $arg . '"';
  166. }
  167. require Win32;
  168. require Win32::Process;
  169. $priority = eval 'import Win32::Process; NORMAL_PRIORITY_CLASS;';
  170. undef $@;
  171. if (Win32::Process::Create($obj, $app, "@args", 0, $priority, '.') != 0) {
  172. $self->{launched} = 1;
  173. $self->{pid} = $obj;
  174. } else {
  175. my $errno = Win32::GetLastError();
  176. $self->{launched} = 0;
  177. $self->{error} = Win32::FormatMessage($errno);
  178. $self->{error} =~ s/[\r\n]+$//s;
  179. $self->{errno} = $errno;
  180. }
  181. return $self->{launched};
  182. }
  183. sub _checkWin32 {
  184. my ($self) = @_;
  185. my $result = ($self->{pid}->Wait(0) == 0);
  186. if ($result == 0) {
  187. my $code;
  188. $self->{pid}->GetExitCode($code);
  189. $self->{exitCode} = $code;
  190. }
  191. return $result;
  192. }
  193. ################## Unix implementation ##################
  194. sub _launchUnix {
  195. my ($self, $detach) = @_;
  196. require POSIX;
  197. import POSIX;
  198. require Fcntl;
  199. my ($pid, $pipe, $r, $w);
  200. # Setup a pipe. This is so we can check whether the
  201. # child process's exec() failed.
  202. local($|);
  203. $| = 0;
  204. if (pipe($r, $w) == -1) {
  205. $self->{error} = $!;
  206. $self->{errno} = int($!);
  207. $self->{launched} = 0;
  208. return 0;
  209. }
  210. # Fork and execute the child process.
  211. $pid = fork();
  212. if ($pid == -1) {
  213. # Fork failed
  214. $self->{launched} = 0;
  215. $self->{error} = $!;
  216. $self->{errno} = int($!);
  217. close($r);
  218. close($w);
  219. return 0;
  220. } elsif ($pid == 0) {
  221. # Child process
  222. my ($error, $errno);
  223. close $r;
  224. $^F = 2;
  225. if ($detach) {
  226. # This prevents some lockups.
  227. open(STDOUT, "> /dev/null");
  228. open(STDERR, "> /dev/null");
  229. POSIX::setsid();
  230. }
  231. if ($detach) {
  232. # This creates a zombie process when the child exits.
  233. # Anyone knows a way to fix that without periodically
  234. # calling waitpid?
  235. $pid = fork();
  236. if ($pid == -1) {
  237. $error = $!;
  238. $errno = int($!);
  239. syswrite($w, "$error\n$errno\n");
  240. } elsif ($pid == 0) {
  241. # Child process
  242. POSIX::setsid();
  243. exec(@{$self->{args}});
  244. $error = $!;
  245. $errno = int($!);
  246. syswrite($w, "$error\n$errno\n");
  247. }
  248. } else {
  249. exec(@{$self->{args}});
  250. # Exec failed
  251. $error = $!;
  252. $errno = int($!);
  253. syswrite($w, "$error\n$errno\n");
  254. }
  255. POSIX::_exit(1);
  256. } else {
  257. # Parent process
  258. my ($error, $errno);
  259. close $w;
  260. $error = <$r>;
  261. $error =~ s/[\r\n]//g;
  262. $errno = <$r> if ($error ne '');
  263. $errno =~ s/[\r\n]//g;
  264. if ($error eq '') {
  265. # Success
  266. $self->{pid} = $pid;
  267. $self->{launched} = 1;
  268. return 1;
  269. } else {
  270. # Failed
  271. $self->{launched} = 0;
  272. $self->{error} = $error;
  273. $self->{errno} = $errno;
  274. return 0;
  275. }
  276. }
  277. }
  278. sub _checkUnix {
  279. my ($self) = @_;
  280. import POSIX ':sys_wait_h';
  281. my $wnohang = eval "WNOHANG";
  282. undef $@;
  283. my $ret = waitpid($self->{pid}, $wnohang);
  284. if ($ret == 0) {
  285. return 1;
  286. } else {
  287. $self->{exitCode} = int($? / 256);
  288. return 0;
  289. }
  290. }
  291. 1;