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 30# Module to share code to start and stop the Apache daemon. 31 32use strict; 33use warnings; 34 35use File::Copy; 36use File::Path; 37use File::Spec; 38use File::Spec::Functions; 39use Fcntl ':flock'; 40use IPC::Open2; 41 42use webkitdirs; 43 44BEGIN { 45 use Exporter (); 46 our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); 47 $VERSION = 1.00; 48 @ISA = qw(Exporter); 49 @EXPORT = qw(&getHTTPDPath 50 &getHTTPDConfigPathForTestDirectory 51 &getDefaultConfigForTestDirectory 52 &openHTTPD 53 &closeHTTPD 54 &setShouldWaitForUserInterrupt 55 &waitForHTTPDLock 56 &getWaitTime); 57 %EXPORT_TAGS = ( ); 58 @EXPORT_OK = (); 59} 60 61my $tmpDir = "/tmp"; 62$tmpDir = convertMsysPath($tmpDir) if isMsys(); 63my $httpdLockPrefix = "WebKitHttpd.lock."; 64my $myLockFile; 65my $exclusiveLockFile = File::Spec->catfile($tmpDir, "WebKit.lock"); 66my $httpdPidDir = File::Spec->catfile($tmpDir, "WebKit"); 67my $httpdPidFile = File::Spec->catfile($httpdPidDir, "httpd.pid"); 68my $httpdPid; 69my $waitForUserInterrupt = 0; 70my $waitBeginTime; 71my $waitEndTime; 72 73$SIG{'INT'} = 'handleInterrupt'; 74$SIG{'TERM'} = 'handleInterrupt'; 75 76sub getHTTPDPath 77{ 78 my $httpdPath; 79 if (isDebianBased()) { 80 $httpdPath = "/usr/sbin/apache2"; 81 } elsif (isMsys()) { 82 $httpdPath = 'c:\program files\apache software foundation\apache2.2\bin\httpd.exe'; 83 } else { 84 $httpdPath = "/usr/sbin/httpd"; 85 } 86 return $httpdPath; 87} 88 89sub getDefaultConfigForTestDirectory 90{ 91 my ($testDirectory) = @_; 92 die "No test directory has been specified." unless ($testDirectory); 93 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 102 my @httpdArgs = ( 103 "-f", "$httpdConfig", 104 "-C", "DocumentRoot \"$documentRoot\"", 105 # Setup a link to where the js test templates are stored, use -c so that mod_alias will already be loaded. 106 "-c", "Alias /js-test-resources \"$jsTestResourcesDirectory\"", 107 "-c", "Alias /media-resources \"$mediaResourcesDirectory\"", 108 "-c", "TypesConfig \"$typesConfig\"", 109 "-c", "PidFile \"$httpdPidFile\"", 110 "-c", "ScoreBoardFile \"$httpdScoreBoardFile\"", 111 ); 112 113 push @httpdArgs, ( 114 # Apache wouldn't run CGIs with permissions==700 otherwise 115 "-c", "User \"#$<\"", 116 "-c", "LockFile \"$httpdLockFile\"" 117 ) unless isMsys(); 118 119 # FIXME: Enable this on Windows once <rdar://problem/5345985> is fixed 120 # The version of Apache we use with Cygwin does not support SSL 121 my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem"; 122 push(@httpdArgs, "-c", "SSLCertificateFile \"$sslCertificate\"") unless isCygwin(); 123 124 return @httpdArgs; 125 126} 127 128sub getHTTPDConfigPathForTestDirectory 129{ 130 my ($testDirectory) = @_; 131 die "No test directory has been specified." unless ($testDirectory); 132 my $httpdConfig; 133 my $httpdPath = getHTTPDPath(); 134 if (isCygwin()) { 135 my $windowsConfDirectory = "$testDirectory/http/conf/"; 136 unless (-x "/usr/lib/apache/libphp4.dll") { 137 copy("$windowsConfDirectory/libphp4.dll", "/usr/lib/apache/libphp4.dll"); 138 chmod(0755, "/usr/lib/apache/libphp4.dll"); 139 } 140 $httpdConfig = "$windowsConfDirectory/cygwin-httpd.conf"; 141 } elsif (isMsys()) { 142 $httpdConfig = "$testDirectory/http/conf/apache2-msys-httpd.conf"; 143 } elsif (isDebianBased()) { 144 $httpdConfig = "$testDirectory/http/conf/apache2-debian-httpd.conf"; 145 } elsif (isFedoraBased()) { 146 $httpdConfig = "$testDirectory/http/conf/fedora-httpd.conf"; 147 } else { 148 $httpdConfig = "$testDirectory/http/conf/httpd.conf"; 149 $httpdConfig = "$testDirectory/http/conf/apache2-httpd.conf" if `$httpdPath -v` =~ m|Apache/2|; 150 } 151 return $httpdConfig; 152} 153 154sub openHTTPD(@) 155{ 156 my (@args) = @_; 157 die "No HTTPD configuration has been specified" unless (@args); 158 mkdir($httpdPidDir, 0755); 159 die "No write permissions to $httpdPidDir" unless (-w $httpdPidDir); 160 161 if (-f $httpdPidFile) { 162 open (PIDFILE, $httpdPidFile); 163 my $oldPid = <PIDFILE>; 164 chomp $oldPid; 165 close PIDFILE; 166 if (0 != kill 0, $oldPid) { 167 print "\nhttpd is already running: pid $oldPid, killing...\n"; 168 if (!killHTTPD($oldPid)) { 169 cleanUp(); 170 die "Timed out waiting for httpd to quit"; 171 } 172 } 173 unlink $httpdPidFile; 174 } 175 176 my $httpdPath = getHTTPDPath(); 177 178 open2(">&1", \*HTTPDIN, $httpdPath, @args); 179 180 my $retryCount = 20; 181 while (!-f $httpdPidFile && $retryCount) { 182 sleep 1; 183 --$retryCount; 184 } 185 186 if (!$retryCount) { 187 cleanUp(); 188 die "Timed out waiting for httpd to start"; 189 } 190 191 $httpdPid = <PIDFILE> if open(PIDFILE, $httpdPidFile); 192 chomp $httpdPid if $httpdPid; 193 close PIDFILE; 194 195 waitpid($httpdPid, 0) if ($waitForUserInterrupt && $httpdPid); 196 197 return 1; 198} 199 200sub closeHTTPD 201{ 202 close HTTPDIN; 203 my $succeeded = killHTTPD($httpdPid); 204 cleanUp(); 205 unless ($succeeded) { 206 print STDERR "Timed out waiting for httpd to terminate!\n" unless $succeeded; 207 return 0; 208 } 209 return 1; 210} 211 212sub killHTTPD 213{ 214 my ($pid) = @_; 215 216 return 1 unless $pid; 217 218 kill 15, $pid; 219 220 my $retryCount = 20; 221 while (kill(0, $pid) && $retryCount) { 222 sleep 1; 223 --$retryCount; 224 } 225 return $retryCount != 0; 226} 227 228sub setShouldWaitForUserInterrupt 229{ 230 $waitForUserInterrupt = 1; 231} 232 233sub handleInterrupt 234{ 235 # On Cygwin, when we receive a signal Apache is still running, so we need 236 # to kill it. On other platforms (at least Mac OS X), Apache will have 237 # already been killed, and trying to kill it again will cause us to hang. 238 # All we need to do in this case is clean up our own files. 239 if (isCygwin()) { 240 closeHTTPD(); 241 } else { 242 cleanUp(); 243 } 244 245 print "\n"; 246 exit(1); 247} 248 249sub cleanUp 250{ 251 rmdir $httpdPidDir; 252 unlink $exclusiveLockFile; 253 unlink $myLockFile if $myLockFile; 254} 255 256sub extractLockNumber 257{ 258 my ($lockFile) = @_; 259 return -1 unless $lockFile; 260 return substr($lockFile, length($httpdLockPrefix)); 261} 262 263sub getLockFiles 264{ 265 opendir(TMPDIR, $tmpDir) or die "Could not open " . $tmpDir . "."; 266 my @lockFiles = grep {m/^$httpdLockPrefix\d+$/} readdir(TMPDIR); 267 @lockFiles = sort { extractLockNumber($a) <=> extractLockNumber($b) } @lockFiles; 268 closedir(TMPDIR); 269 return @lockFiles; 270} 271 272sub getNextAvailableLockNumber 273{ 274 my @lockFiles = getLockFiles(); 275 return 0 unless @lockFiles; 276 return extractLockNumber($lockFiles[-1]) + 1; 277} 278 279sub getLockNumberForCurrentRunning 280{ 281 my @lockFiles = getLockFiles(); 282 return 0 unless @lockFiles; 283 return extractLockNumber($lockFiles[0]); 284} 285 286sub waitForHTTPDLock 287{ 288 $waitBeginTime = time; 289 scheduleHttpTesting(); 290 # If we are the only one waiting for Apache just run the tests without any further checking 291 if (scalar getLockFiles() > 1) { 292 my $currentLockFile = File::Spec->catfile($tmpDir, "$httpdLockPrefix" . getLockNumberForCurrentRunning()); 293 my $currentLockPid = <SCHEDULER_LOCK> if (-f $currentLockFile && open(SCHEDULER_LOCK, "<$currentLockFile")); 294 # Wait until we are allowed to run the http tests 295 while ($currentLockPid && $currentLockPid != $$) { 296 $currentLockFile = File::Spec->catfile($tmpDir, "$httpdLockPrefix" . getLockNumberForCurrentRunning()); 297 if ($currentLockFile eq $myLockFile) { 298 $currentLockPid = <SCHEDULER_LOCK> if open(SCHEDULER_LOCK, "<$currentLockFile"); 299 if ($currentLockPid != $$) { 300 print STDERR "\nPID mismatch.\n"; 301 last; 302 } 303 } else { 304 sleep 1; 305 } 306 } 307 } 308 $waitEndTime = time; 309} 310 311sub scheduleHttpTesting 312{ 313 # We need an exclusive lock file to avoid deadlocks and starvation and ensure that the scheduler lock numbers are sequential. 314 # The scheduler locks are used to schedule the running test sessions in first come first served order. 315 while (!(open(SEQUENTIAL_GUARD_LOCK, ">$exclusiveLockFile") && flock(SEQUENTIAL_GUARD_LOCK, LOCK_EX|LOCK_NB))) {} 316 $myLockFile = File::Spec->catfile($tmpDir, "$httpdLockPrefix" . getNextAvailableLockNumber()); 317 open(SCHEDULER_LOCK, ">$myLockFile"); 318 print SCHEDULER_LOCK "$$"; 319 print SEQUENTIAL_GUARD_LOCK "$$"; 320 close(SCHEDULER_LOCK); 321 close(SEQUENTIAL_GUARD_LOCK); 322 unlink $exclusiveLockFile; 323} 324 325sub getWaitTime 326{ 327 my $waitTime = 0; 328 if ($waitBeginTime && $waitEndTime) { 329 $waitTime = $waitEndTime - $waitBeginTime; 330 } 331 return $waitTime; 332} 333 334sub convertMsysPath 335{ 336 my ($path) = @_; 337 return unless isMsys(); 338 339 $path = `cmd.exe //c echo $path`; 340 $path =~ s/\r\n$//; 341 return $path; 342} 343