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