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