1 # Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved 2 # Copyright (C) 2006 Alexey Proskuryakov (ap (at] nypop.com) 3 # Copyright (C) 2010 Andras Becsi (abecsi (at] 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 32 use strict; 33 use warnings; 34 35 use File::Copy; 36 use File::Path; 37 use File::Spec; 38 use File::Spec::Functions; 39 use Fcntl ':flock'; 40 use IPC::Open2; 41 42 use webkitdirs; 43 44 BEGIN { 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 61 my $tmpDir = "/tmp"; 62 $tmpDir = convertMsysPath($tmpDir) if isMsys(); 63 my $httpdLockPrefix = "WebKitHttpd.lock."; 64 my $myLockFile; 65 my $exclusiveLockFile = File::Spec->catfile($tmpDir, "WebKit.lock"); 66 my $httpdPidDir = File::Spec->catfile($tmpDir, "WebKit"); 67 my $httpdPidFile = File::Spec->catfile($httpdPidDir, "httpd.pid"); 68 my $httpdPid; 69 my $waitForUserInterrupt = 0; 70 my $waitBeginTime; 71 my $waitEndTime; 72 73 $SIG{'INT'} = 'handleInterrupt'; 74 $SIG{'TERM'} = 'handleInterrupt'; 75 76 sub 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 89 sub 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 128 sub 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 154 sub 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 200 sub 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 212 sub 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 228 sub setShouldWaitForUserInterrupt 229 { 230 $waitForUserInterrupt = 1; 231 } 232 233 sub 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 249 sub cleanUp 250 { 251 rmdir $httpdPidDir; 252 unlink $exclusiveLockFile; 253 unlink $myLockFile if $myLockFile; 254 } 255 256 sub extractLockNumber 257 { 258 my ($lockFile) = @_; 259 return -1 unless $lockFile; 260 return substr($lockFile, length($httpdLockPrefix)); 261 } 262 263 sub 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 272 sub getNextAvailableLockNumber 273 { 274 my @lockFiles = getLockFiles(); 275 return 0 unless @lockFiles; 276 return extractLockNumber($lockFiles[-1]) + 1; 277 } 278 279 sub getLockNumberForCurrentRunning 280 { 281 my @lockFiles = getLockFiles(); 282 return 0 unless @lockFiles; 283 return extractLockNumber($lockFiles[0]); 284 } 285 286 sub 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 311 sub 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 325 sub getWaitTime 326 { 327 my $waitTime = 0; 328 if ($waitBeginTime && $waitEndTime) { 329 $waitTime = $waitEndTime - $waitBeginTime; 330 } 331 return $waitTime; 332 } 333 334 sub 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