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 # 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 31 use strict; 32 use warnings; 33 34 use File::Path; 35 use File::Spec; 36 use File::Spec::Functions; 37 use Fcntl ':flock'; 38 use IPC::Open2; 39 40 use webkitdirs; 41 42 BEGIN { 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 59 my $tmpDir = "/tmp"; 60 my $httpdLockPrefix = "WebKitHttpd.lock."; 61 my $myLockFile; 62 my $exclusiveLockFile = File::Spec->catfile($tmpDir, "WebKit.lock"); 63 my $httpdPath; 64 my $httpdPidDir = File::Spec->catfile($tmpDir, "WebKit"); 65 my $httpdPidFile = File::Spec->catfile($httpdPidDir, "httpd.pid"); 66 my $httpdPid; 67 my $waitForUserInterrupt = 0; 68 my $waitBeginTime; 69 my $waitEndTime; 70 71 $SIG{'INT'} = 'handleInterrupt'; 72 $SIG{'TERM'} = 'handleInterrupt'; 73 74 sub getHTTPDPath 75 { 76 if (isDebianBased()) { 77 $httpdPath = "/usr/sbin/apache2"; 78 } else { 79 $httpdPath = "/usr/sbin/httpd"; 80 } 81 return $httpdPath; 82 } 83 84 sub 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 118 sub 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 142 sub 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 195 sub 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 214 sub setShouldWaitForUserInterrupt 215 { 216 $waitForUserInterrupt = 1; 217 } 218 219 sub handleInterrupt 220 { 221 closeHTTPD(); 222 print "\n"; 223 exit(1); 224 } 225 226 sub cleanUp 227 { 228 rmdir $httpdPidDir; 229 unlink $exclusiveLockFile; 230 unlink $myLockFile if $myLockFile; 231 } 232 233 sub extractLockNumber 234 { 235 my ($lockFile) = @_; 236 return -1 unless $lockFile; 237 return substr($lockFile, length($httpdLockPrefix)); 238 } 239 240 sub 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 249 sub getNextAvailableLockNumber 250 { 251 my @lockFiles = getLockFiles(); 252 return 0 unless @lockFiles; 253 return extractLockNumber($lockFiles[-1]) + 1; 254 } 255 256 sub getLockNumberForCurrentRunning 257 { 258 my @lockFiles = getLockFiles(); 259 return 0 unless @lockFiles; 260 return extractLockNumber($lockFiles[0]); 261 } 262 263 sub 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 288 sub 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 302 sub getWaitTime 303 { 304 my $waitTime = 0; 305 if ($waitBeginTime && $waitEndTime) { 306 $waitTime = $waitEndTime - $waitBeginTime; 307 } 308 return $waitTime; 309 } 310