httpd.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. # Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved
  2. # Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
  3. # Copyright (C) 2010 Andras Becsi (abecsi@inf.u-szeged.hu), University of Szeged
  4. # Copyright (C) 2011 Research In Motion Limited. All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. #
  10. # 1. Redistributions of source code must retain the above copyright
  11. # notice, this list of conditions and the following disclaimer.
  12. # 2. Redistributions in binary form must reproduce the above copyright
  13. # notice, this list of conditions and the following disclaimer in the
  14. # documentation and/or other materials provided with the distribution.
  15. # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  16. # its contributors may be used to endorse or promote products derived
  17. # from this software without specific prior written permission.
  18. #
  19. # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  20. # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  21. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  22. # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  23. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  24. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  25. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  26. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  28. # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. # Module to share code to start and stop the Apache daemon.
  30. use strict;
  31. use warnings;
  32. use File::Copy;
  33. use File::Path;
  34. use File::Spec;
  35. use File::Spec::Functions;
  36. use Fcntl ':flock';
  37. use IPC::Open2;
  38. use webkitdirs;
  39. BEGIN {
  40. use Exporter ();
  41. our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
  42. $VERSION = 1.00;
  43. @ISA = qw(Exporter);
  44. @EXPORT = qw(&getHTTPDPath
  45. &hasHTTPD
  46. &getHTTPDConfigPathForTestDirectory
  47. &getDefaultConfigForTestDirectory
  48. &openHTTPD
  49. &closeHTTPD
  50. &setShouldWaitForUserInterrupt
  51. &waitForHTTPDLock
  52. &getWaitTime);
  53. %EXPORT_TAGS = ( );
  54. @EXPORT_OK = ();
  55. }
  56. my $tmpDir = "/tmp";
  57. my $httpdLockPrefix = "WebKitHttpd.lock.";
  58. my $myLockFile;
  59. my $exclusiveLockFile = File::Spec->catfile($tmpDir, "WebKit.lock");
  60. my $httpdPidDir = File::Spec->catfile($tmpDir, "WebKit");
  61. my $httpdPidFile = File::Spec->catfile($httpdPidDir, "httpd.pid");
  62. my $httpdPid;
  63. my $waitForUserInterrupt = 0;
  64. my $waitBeginTime;
  65. my $waitEndTime;
  66. $SIG{'INT'} = 'handleInterrupt';
  67. $SIG{'TERM'} = 'handleInterrupt';
  68. sub getHTTPDPath
  69. {
  70. my $httpdPath;
  71. if (isDebianBased()) {
  72. $httpdPath = "/usr/sbin/apache2";
  73. } else {
  74. $httpdPath = "/usr/sbin/httpd";
  75. }
  76. return $httpdPath;
  77. }
  78. sub hasHTTPD
  79. {
  80. my @command = (getHTTPDPath(), "-v");
  81. return system(@command) == 0;
  82. }
  83. sub getApacheVersion
  84. {
  85. my $httpdPath = getHTTPDPath();
  86. my $version = `$httpdPath -v`;
  87. $version =~ s/.*Server version: Apache\/(\d+\.\d+).*/$1/s;
  88. return $version;
  89. }
  90. sub getDefaultConfigForTestDirectory
  91. {
  92. my ($testDirectory) = @_;
  93. die "No test directory has been specified." unless ($testDirectory);
  94. my $httpdConfig = getHTTPDConfigPathForTestDirectory($testDirectory);
  95. my $documentRoot = "$testDirectory/http/tests";
  96. my $jsTestResourcesDirectory = $testDirectory . "/fast/js/resources";
  97. my $mediaResourcesDirectory = $testDirectory . "/media";
  98. my $typesConfig = "$testDirectory/http/conf/mime.types";
  99. my $httpdLockFile = File::Spec->catfile($httpdPidDir, "httpd.lock");
  100. my $httpdScoreBoardFile = File::Spec->catfile($httpdPidDir, "httpd.scoreboard");
  101. my @httpdArgs = (
  102. "-f", "$httpdConfig",
  103. "-C", "DocumentRoot \"$documentRoot\"",
  104. # Setup a link to where the js test templates are stored, use -c so that mod_alias will already be loaded.
  105. "-c", "Alias /js-test-resources \"$jsTestResourcesDirectory\"",
  106. "-c", "Alias /media-resources \"$mediaResourcesDirectory\"",
  107. "-c", "TypesConfig \"$typesConfig\"",
  108. # Apache wouldn't run CGIs with permissions==700 otherwise
  109. "-c", "User \"#$<\"",
  110. "-c", "PidFile \"$httpdPidFile\"",
  111. "-c", "ScoreBoardFile \"$httpdScoreBoardFile\"",
  112. );
  113. if (getApacheVersion() eq "2.2") {
  114. push(@httpdArgs, "-c", "LockFile \"$httpdLockFile\"");
  115. }
  116. # FIXME: Enable this on Windows once <rdar://problem/5345985> is fixed
  117. # The version of Apache we use with Cygwin does not support SSL
  118. my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem";
  119. push(@httpdArgs, "-c", "SSLCertificateFile \"$sslCertificate\"") unless isCygwin();
  120. return @httpdArgs;
  121. }
  122. sub getHTTPDConfigPathForTestDirectory
  123. {
  124. my ($testDirectory) = @_;
  125. die "No test directory has been specified." unless ($testDirectory);
  126. my $httpdConfig;
  127. my $httpdPath = getHTTPDPath();
  128. my $httpdConfDirectory = "$testDirectory/http/conf/";
  129. my $apacheVersion = getApacheVersion();
  130. if (isCygwin()) {
  131. my $libPHP4DllPath = "/usr/lib/apache/libphp4.dll";
  132. # FIXME: run-webkit-tests should not modify the user's system, especially not in this method!
  133. unless (-x $libPHP4DllPath) {
  134. copy("$httpdConfDirectory/libphp4.dll", $libPHP4DllPath);
  135. chmod(0755, $libPHP4DllPath);
  136. }
  137. $httpdConfig = "cygwin-httpd.conf"; # This is an apache 1.3 config.
  138. } elsif (isDebianBased()) {
  139. $httpdConfig = "apache2-debian-httpd.conf";
  140. } elsif (isFedoraBased()) {
  141. $httpdConfig = "fedora-httpd-$apacheVersion.conf";
  142. } else {
  143. # All other ports use apache2, so just use our default apache2 config.
  144. $httpdConfig = "apache2-httpd.conf";
  145. }
  146. return "$httpdConfDirectory/$httpdConfig";
  147. }
  148. sub openHTTPD(@)
  149. {
  150. my (@args) = @_;
  151. die "No HTTPD configuration has been specified" unless (@args);
  152. mkdir($httpdPidDir, 0755);
  153. die "No write permissions to $httpdPidDir" unless (-w $httpdPidDir);
  154. if (-f $httpdPidFile) {
  155. open (PIDFILE, $httpdPidFile);
  156. my $oldPid = <PIDFILE>;
  157. chomp $oldPid;
  158. close PIDFILE;
  159. if (0 != kill 0, $oldPid) {
  160. print "\nhttpd is already running: pid $oldPid, killing...\n";
  161. if (!killHTTPD($oldPid)) {
  162. cleanUp();
  163. die "Timed out waiting for httpd to quit";
  164. }
  165. }
  166. unlink $httpdPidFile;
  167. }
  168. my $httpdPath = getHTTPDPath();
  169. open2(">&1", \*HTTPDIN, $httpdPath, @args);
  170. my $retryCount = 20;
  171. while (!-f $httpdPidFile && $retryCount) {
  172. sleep 1;
  173. --$retryCount;
  174. }
  175. if (!$retryCount) {
  176. cleanUp();
  177. die "Timed out waiting for httpd to start";
  178. }
  179. $httpdPid = <PIDFILE> if open(PIDFILE, $httpdPidFile);
  180. chomp $httpdPid if $httpdPid;
  181. close PIDFILE;
  182. waitpid($httpdPid, 0) if ($waitForUserInterrupt && $httpdPid);
  183. return 1;
  184. }
  185. sub closeHTTPD
  186. {
  187. close HTTPDIN;
  188. my $succeeded = killHTTPD($httpdPid);
  189. cleanUp();
  190. unless ($succeeded) {
  191. print STDERR "Timed out waiting for httpd to terminate!\n" unless $succeeded;
  192. return 0;
  193. }
  194. return 1;
  195. }
  196. sub killHTTPD
  197. {
  198. my ($pid) = @_;
  199. return 1 unless $pid;
  200. kill 15, $pid;
  201. my $retryCount = 20;
  202. while (kill(0, $pid) && $retryCount) {
  203. sleep 1;
  204. --$retryCount;
  205. }
  206. return $retryCount != 0;
  207. }
  208. sub setShouldWaitForUserInterrupt
  209. {
  210. $waitForUserInterrupt = 1;
  211. }
  212. sub handleInterrupt
  213. {
  214. # On Cygwin, when we receive a signal Apache is still running, so we need
  215. # to kill it. On other platforms (at least Mac OS X), Apache will have
  216. # already been killed, and trying to kill it again will cause us to hang.
  217. # All we need to do in this case is clean up our own files.
  218. if (isCygwin()) {
  219. closeHTTPD();
  220. } else {
  221. cleanUp();
  222. }
  223. print "\n";
  224. exit(1);
  225. }
  226. sub cleanUp
  227. {
  228. rmdir $httpdPidDir;
  229. unlink $exclusiveLockFile;
  230. unlink $myLockFile if $myLockFile;
  231. }
  232. sub extractLockNumber
  233. {
  234. my ($lockFile) = @_;
  235. return -1 unless $lockFile;
  236. return substr($lockFile, length($httpdLockPrefix));
  237. }
  238. sub getLockFiles
  239. {
  240. opendir(TMPDIR, $tmpDir) or die "Could not open " . $tmpDir . ".";
  241. my @lockFiles = grep {m/^$httpdLockPrefix\d+$/} readdir(TMPDIR);
  242. @lockFiles = sort { extractLockNumber($a) <=> extractLockNumber($b) } @lockFiles;
  243. closedir(TMPDIR);
  244. return @lockFiles;
  245. }
  246. sub getNextAvailableLockNumber
  247. {
  248. my @lockFiles = getLockFiles();
  249. return 0 unless @lockFiles;
  250. return extractLockNumber($lockFiles[-1]) + 1;
  251. }
  252. sub getLockNumberForCurrentRunning
  253. {
  254. my @lockFiles = getLockFiles();
  255. return 0 unless @lockFiles;
  256. return extractLockNumber($lockFiles[0]);
  257. }
  258. sub waitForHTTPDLock
  259. {
  260. $waitBeginTime = time;
  261. scheduleHttpTesting();
  262. # If we are the only one waiting for Apache just run the tests without any further checking
  263. if (scalar getLockFiles() > 1) {
  264. my $currentLockFile = File::Spec->catfile($tmpDir, "$httpdLockPrefix" . getLockNumberForCurrentRunning());
  265. my $currentLockPid = <SCHEDULER_LOCK> if (-f $currentLockFile && open(SCHEDULER_LOCK, "<$currentLockFile"));
  266. # Wait until we are allowed to run the http tests
  267. while ($currentLockPid && $currentLockPid != $$) {
  268. $currentLockFile = File::Spec->catfile($tmpDir, "$httpdLockPrefix" . getLockNumberForCurrentRunning());
  269. if ($currentLockFile eq $myLockFile) {
  270. $currentLockPid = <SCHEDULER_LOCK> if open(SCHEDULER_LOCK, "<$currentLockFile");
  271. if ($currentLockPid != $$) {
  272. print STDERR "\nPID mismatch.\n";
  273. last;
  274. }
  275. } else {
  276. sleep 1;
  277. }
  278. }
  279. }
  280. $waitEndTime = time;
  281. }
  282. sub scheduleHttpTesting
  283. {
  284. # We need an exclusive lock file to avoid deadlocks and starvation and ensure that the scheduler lock numbers are sequential.
  285. # The scheduler locks are used to schedule the running test sessions in first come first served order.
  286. while (!(open(SEQUENTIAL_GUARD_LOCK, ">$exclusiveLockFile") && flock(SEQUENTIAL_GUARD_LOCK, LOCK_EX|LOCK_NB))) {}
  287. $myLockFile = File::Spec->catfile($tmpDir, "$httpdLockPrefix" . getNextAvailableLockNumber());
  288. open(SCHEDULER_LOCK, ">$myLockFile");
  289. print SCHEDULER_LOCK "$$";
  290. print SEQUENTIAL_GUARD_LOCK "$$";
  291. close(SCHEDULER_LOCK);
  292. close(SEQUENTIAL_GUARD_LOCK);
  293. unlink $exclusiveLockFile;
  294. }
  295. sub getWaitTime
  296. {
  297. my $waitTime = 0;
  298. if ($waitBeginTime && $waitEndTime) {
  299. $waitTime = $waitEndTime - $waitBeginTime;
  300. }
  301. return $waitTime;
  302. }