1 # |
|
2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 # All rights reserved. |
|
4 # This component and the accompanying materials are made available |
|
5 # under the terms of "Eclipse Public License v1.0" |
|
6 # which accompanies this distribution, and is available |
|
7 # at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 # |
|
9 # Initial Contributors: |
|
10 # Nokia Corporation - initial contribution. |
|
11 # |
|
12 # Contributors: |
|
13 # |
|
14 # Description: |
|
15 # |
|
16 |
|
17 package AntiVirus; |
|
18 use strict; |
|
19 |
|
20 use File::Copy; |
|
21 use Win32::Process; |
|
22 |
|
23 require Exporter; |
|
24 our @ISA = qw(Exporter); |
|
25 our @EXPORT_OK = qw{Start Stop Scan WaitTillAntiVirusStarts}; |
|
26 |
|
27 # Start: |
|
28 # Description: attempts to start all supported anti-virus services/processes |
|
29 # Revised April 2007 to support McAfee only, Sophos and WebRoot having passed into history! |
|
30 # Arguments: none |
|
31 # Returns: none |
|
32 sub Start() |
|
33 { |
|
34 my @iMcAfeeCommands = ( |
|
35 {AppName => 'net.exe', Params => 'start McAfeeFramework'}, |
|
36 {AppName => 'net.exe', Params => 'start McShield'}, |
|
37 {AppName => 'net.exe', Params => 'start McTaskManager'}, |
|
38 # Must supply full pathname, as these applications are not in "PATH" |
|
39 {AppName => 'C:\\Program Files\\Network Associates\\Common Framework\\UpdaterUI.exe', Params => '/StartedFromRunKey'}, |
|
40 {AppName => 'C:\\Program Files\\Network Associates\\Common Framework\\UdaterUI.exe', Params => '/StartedFromRunKey'}, |
|
41 {AppName => 'C:\\Program Files\\McAfee\\Common Framework\\UpdaterUI.exe', Params => '/StartedFromRunKey'}, |
|
42 {AppName => 'C:\\Program Files\\McAfee\\Common Framework\\UdaterUI.exe', Params => '/StartedFromRunKey'}, |
|
43 {AppName => 'C:\\Program Files\\Common Files\\Network Associates\\TalkBack\\tbmon.exe', Params => ''}, |
|
44 {AppName => 'C:\\Program Files\\Network Associates\\VirusScan\\SHSTAT.EXE', Params => '/STANDALONE'}, |
|
45 {AppName => 'C:\\Program Files\\McAfee\\VirusScan\\SHSTAT.EXE', Params => '/STANDALONE'}); |
|
46 |
|
47 # Execute all "START" commands |
|
48 for my $iCmd(@iMcAfeeCommands) |
|
49 { |
|
50 ExecuteProcess(20,$iCmd); |
|
51 } |
|
52 |
|
53 } |
|
54 |
|
55 # WaitTillAntiVirusStarts |
|
56 # Description: |
|
57 # Sometimes the Antivirus::Stop() is invoked before the Antivirus services are started in the machine. |
|
58 # because of which the Antivirus starts when the build is ongoing, and the active Antivirus disrupts/slows down the build |
|
59 # |
|
60 # If the Antivirus services fail to start beyond the specified timeout period,reports error and aborts the build. |
|
61 # |
|
62 # Arguments : Waiting time in seconds before next attempt is made to check AV service status, max number of attempts |
|
63 # Returns : returns nothing if AV running, aborts build if AV not running even after the waiting period |
|
64 sub WaitTillAntiVirusStarts{ |
|
65 my $waitingTime = shift; |
|
66 my $retries = shift; |
|
67 |
|
68 my @waitList = shift; |
|
69 |
|
70 my $defaultWaitingTime = 30; # Waiting time in seconds, before next attempt is made to check AV service status |
|
71 my $defaultRetries = 5; # Try upto 5 times |
|
72 |
|
73 # assign default values to waitingtime,retries if values not specified |
|
74 unless($waitingTime) |
|
75 { |
|
76 $waitingTime = $defaultWaitingTime; |
|
77 } |
|
78 unless($retries) |
|
79 { |
|
80 $retries = $defaultRetries; |
|
81 } |
|
82 |
|
83 my $attempt = 0; |
|
84 ## If AV not active, wait and retry |
|
85 while(1) |
|
86 { |
|
87 if(IsAntiVirusActive(@waitList)) |
|
88 { |
|
89 return; ## AV is active, all fine, proceed |
|
90 } |
|
91 $attempt++; |
|
92 if($attempt > $retries) |
|
93 { |
|
94 last; |
|
95 } |
|
96 print "REMARK: One or more Antivirus services not active yet (At ".scalar localtime().") \n"; |
|
97 print "Waiting $waitingTime secs before rechecking status (Attempt $attempt of $retries) ...\n\n"; |
|
98 sleep($waitingTime); |
|
99 } |
|
100 |
|
101 ## Antivirus is not active even after waiting $waitingTime secs $retries times, report error and abort the build |
|
102 |
|
103 print "ERROR: RealTimeBuild : Antivirus is not active even after waiting for $waitingTime secs x $retries attempts\n"; |
|
104 print "Antivirus cannot be reliably stopped,abandon build."; |
|
105 } |
|
106 |
|
107 |
|
108 # IsAntiVirusActive |
|
109 # Description: |
|
110 # Runs 'net start' command to get a list of active services,checks if the Antivirus services are in the list |
|
111 # |
|
112 # Arguments : None |
|
113 # Returns : |
|
114 # returns 0 even if one Antivirus Service is inactive |
|
115 # returns 1 if All AntiVirus Services active |
|
116 sub IsAntiVirusActive{ |
|
117 |
|
118 my @waitList = shift; |
|
119 |
|
120 my @AntiVirusServices = @waitList; |
|
121 |
|
122 print "Checking Antivirus services status ...\n"; |
|
123 my $iCmd={AppName => 'net.exe', Params => 'start'}; |
|
124 |
|
125 # Check if net.exe is accessible |
|
126 unless(-e $iCmd->{'AppName'}) |
|
127 { |
|
128 my $iExecutable = FindFileFromPath($iCmd->{'AppName'}); |
|
129 unless ($iExecutable) |
|
130 { |
|
131 print "REMARK: File not found $iCmd->{'AppName'}\n"; |
|
132 print "REMARK: Unable to check Antivirus status\n"; |
|
133 return 0; |
|
134 } |
|
135 $iCmd->{'AppName'} = $iExecutable; |
|
136 } |
|
137 |
|
138 my $iCommand = "\"$iCmd->{'AppName'}\" $iCmd->{'Params'}" ; |
|
139 print "Running: $iCommand\n"; |
|
140 my @iOutput = `$iCommand`; |
|
141 |
|
142 return &ParseServiceStatus(\@iOutput,\@AntiVirusServices); |
|
143 } |
|
144 |
|
145 |
|
146 # ParseServiceStatus |
|
147 # Description: |
|
148 # Runs Compares output of 'net start' command to get a list of active services,checks if the Antivirus services are in the list |
|
149 # |
|
150 # Arguments : Output of 'net start', Antivirus service list |
|
151 # Returns : |
|
152 # returns 0 even if one Antivirus Service is not in the list |
|
153 # returns 1 if All AntiVirus Services are in the list |
|
154 sub ParseServiceStatus($$) |
|
155 { |
|
156 my $iOutputRef = shift; |
|
157 my $iAntiVirusServiceList = shift; |
|
158 foreach(@$iOutputRef) |
|
159 { |
|
160 chomp ; |
|
161 s/^[\t\s]+//g; # strip whitespace at the beginning of the line |
|
162 } |
|
163 |
|
164 for my $serviceName(@$iAntiVirusServiceList) |
|
165 { |
|
166 my @match = grep (/^$serviceName$/i, @$iOutputRef); |
|
167 unless(@match) |
|
168 { |
|
169 print "Antivirus Service \'$serviceName\' inactive\n"; |
|
170 return 0; |
|
171 } |
|
172 } |
|
173 print "All Antivirus services active.\n"; |
|
174 return 1; |
|
175 } |
|
176 |
|
177 |
|
178 # Stop: |
|
179 # Description: attempts to stop all supported anti-virus services/processes |
|
180 # Revised April 2007 to support McAfee only, Sophos and WebRoot having passed into history! |
|
181 # Arguments: none |
|
182 # Returns: none |
|
183 sub Stop($$) |
|
184 { |
|
185 # Need to check if the Antivirus services is installed or enabled. |
|
186 my @iMcAfeeServices = ( |
|
187 {AppName => 'net.exe', Params => 'stop McAfeeFramework', Services =>'McAfee Framework Service'}, |
|
188 {AppName => 'net.exe', Params => 'stop McShield', Services =>'Network Associates McShield'}, |
|
189 {AppName => 'net.exe', Params => 'stop McTaskManager', Services =>'Network Associates Task Manager'}); |
|
190 |
|
191 my @iPskillCommands = ( |
|
192 {AppName => 'pskill.exe', Params => 'UpdaterUI.exe'}, |
|
193 {AppName => 'pskill.exe', Params => 'tbmon.exe'}, |
|
194 {AppName => 'pskill.exe', Params => 'shstat.exe'}); |
|
195 |
|
196 my @waitList = (); |
|
197 my @iMcAfeeCommands = (); |
|
198 |
|
199 for my $iTestCmd(@iMcAfeeServices) |
|
200 { |
|
201 my $iExecutable = $iTestCmd->{'AppName'}; |
|
202 my $iParams = $iTestCmd->{'Params'}; |
|
203 my $iServices = $iTestCmd->{'Services'}; |
|
204 |
|
205 my @testCommand = `$iExecutable $iParams 2>&1`; |
|
206 my $iResponse = 0; |
|
207 |
|
208 foreach my $iLine (@testCommand) |
|
209 { |
|
210 if ($iLine =~ m/does not exist/i) |
|
211 { |
|
212 print "REMARK: $iServices not installed, just proceed.\n"; |
|
213 $iResponse = 1; |
|
214 } |
|
215 |
|
216 if ($iLine =~ m/it is disabled/i) |
|
217 { |
|
218 print "REMARK: $iServices not enabled, just proceed.\n"; |
|
219 $iResponse = 1; |
|
220 } |
|
221 |
|
222 if (($iLine =~ m/service is not started/i ) or ($iLine =~ m/not valid for this service/i )) |
|
223 { |
|
224 print "REMARK: $iServices not started, just wait.\n"; |
|
225 push @waitList, $iServices; |
|
226 push @iMcAfeeCommands, {AppName =>$iExecutable, Params =>$iParams}; |
|
227 $iResponse = 1; |
|
228 } |
|
229 |
|
230 if ($iLine =~ m/service was stopped successfully/i) |
|
231 { |
|
232 print "REMARK: Stop Success! $iServices was stopped successfully directly by the test command, just proceed.\n"; |
|
233 $iResponse = 1; |
|
234 } |
|
235 |
|
236 if ($iLine =~ m/service could not be stopped/i) |
|
237 { |
|
238 print "REMARK: Stop Success! $iServices could not be stopped at the moment but will be stopped successfully directly by the test command very soon later, just proceed.\n"; |
|
239 $iResponse = 1; |
|
240 } |
|
241 |
|
242 if ($iLine =~ m/System error 5 has occurred/i) |
|
243 { |
|
244 print "WARNING: Access to $iServices is denied, but this won't affect the build, just proceed.\n"; |
|
245 $iResponse = 1; |
|
246 } |
|
247 |
|
248 } |
|
249 unless ($iResponse) |
|
250 { |
|
251 print "ERROR: Unable to parse the output of $iExecutable for $iParams!\n"; |
|
252 foreach my $iLine (@testCommand) |
|
253 { |
|
254 print $iLine; |
|
255 } |
|
256 #push @waitList, $iServices; |
|
257 #push @iMcAfeeCommands, {AppName =>$iExecutable, Params =>$iParams}; |
|
258 } |
|
259 } |
|
260 |
|
261 # Wait for the waiting Antivirus services to load before attempting to stop the services |
|
262 # If Antivirus fails to load after the grace period, the build should be aborted. |
|
263 my $gWaitingTime = shift; |
|
264 my $gRetries = shift; |
|
265 if (@waitList) |
|
266 { |
|
267 WaitTillAntiVirusStarts($gWaitingTime, $gRetries, @waitList); |
|
268 } |
|
269 |
|
270 # Execute all "STOP" commands |
|
271 push @iMcAfeeCommands, @iPskillCommands; |
|
272 for my $iCmd(@iMcAfeeCommands) |
|
273 { |
|
274 ExecuteProcess(20,$iCmd); |
|
275 } |
|
276 } |
|
277 |
|
278 # Scan: |
|
279 # Description: performs the virus scan on the specified directores |
|
280 # Revised April 2007 to support McAfee only, Sophos having passed into history! |
|
281 # Revised February 2008 to support further McAfee filename changes. |
|
282 # Arguments: directory in which to place scan output file (logfile); |
|
283 # array ref to array containing a list of the directories to scan |
|
284 # Returns: none |
|
285 sub Scan($$) |
|
286 { |
|
287 my $iOutFilesDir = shift; |
|
288 my $iDirsToScan = shift; |
|
289 |
|
290 # Name(s) of output files. User only supplies directories |
|
291 my $iOutFileName = 'Anti-virus'; |
|
292 |
|
293 # Define McAfee commands, newest version first. McAfee keep changing the names of their files. Thus if an |
|
294 # executable is not found, we do not treat this as an error, we simply go on to thwe next command definition. |
|
295 # Note: Must supply full pathname, as these applications are not in "PATH" |
|
296 my @iMcAfeeCommands =( |
|
297 # CSScan.exe send most of its output to STDOUT. So define file a second time for us to write this data |
|
298 {AppName => 'C:\\Program Files\\McAfee\\VirusScan Enterprise\\csscan.exe', |
|
299 Params => join(" ", @$iDirsToScan) . " /SUB /CLEAN /ALLAPPS /LOG $iOutFilesDir\\$iOutFileName.log", |
|
300 LogFile => "$iOutFilesDir\\$iOutFileName"}, # Filename without .LOG extension |
|
301 {AppName => 'C:\\Program Files\\Common Files\\Network Associates\\Engine\\scan.exe', |
|
302 Params => join(" ", @$iDirsToScan) . " /SUB /NOBREAK /CLEAN /NORENAME /RPTCOR /RPTERR /REPORT=$iOutFilesDir\\$iOutFileName.log", |
|
303 LogFile => ''} # The older "SCAN.EXE" writes everything to its report file. So no further logging required. |
|
304 ); |
|
305 |
|
306 print "Scanning: @$iDirsToScan\n"; |
|
307 |
|
308 # Execute "SCAN" commands in order until one is successful. |
|
309 for my $iCmd(@iMcAfeeCommands) |
|
310 { |
|
311 # First remove/rename any existing output file. This is an unlikely situation in a normal build; |
|
312 # but it could cause confusion if, for example, a scan was re-run manually. |
|
313 unless (RemoveExistingFile($iCmd->{'LogFile'})) { next; } |
|
314 if (RunCommand($iCmd)) { return; } # Success |
|
315 } |
|
316 # Arriving at the end of this loop means that no command succeeded! Report failure! |
|
317 print "WARNING: No Virus Scan accomplished. Possibly no suitable executable found.\n"; |
|
318 } |
|
319 |
|
320 # RunCommand: |
|
321 # Description: Runs specified command using Perl "backticks" |
|
322 # passing its output for parsing to the sub ParseOutput(). |
|
323 # Arguments: the command to execute as a hashref (filename of executable and parameters/args to pass to it) |
|
324 # Returns: TRUE on success |
|
325 sub RunCommand($) |
|
326 { |
|
327 my $iCmd = shift; # Hashref - Command spec. |
|
328 |
|
329 unless(-e $iCmd->{'AppName'}) |
|
330 { |
|
331 print "REMARK: File not found $iCmd->{'AppName'}\n"; |
|
332 return 0; # FALSE = Failure |
|
333 } |
|
334 my $iCommand = "\"$iCmd->{'AppName'}\" $iCmd->{'Params'}" ; |
|
335 print "Running: $iCommand\n"; |
|
336 my @iOutput = `$iCommand`; |
|
337 |
|
338 # Parse the output flagging any errors |
|
339 return &ParseOutput(\@iOutput,$iCmd->{'LogFile'}); # Return TRUE or FALSE |
|
340 } |
|
341 |
|
342 # ParseOutput: |
|
343 # Description: Parse output for warnings and errors. |
|
344 # The ScanLog-compatible "WARNING: " is prepended to any line containing |
|
345 # any of these messages or errors, and all lines are printed to STDOUT. |
|
346 # Arguments: reference to the array of output lines from executed command; Name of logfile (if any) |
|
347 # Returns: TRUE on Success |
|
348 # Remarks: note that errors in starting/stopping processes or services do not |
|
349 # constitute errors in terms of the build process. Failure to start or stop |
|
350 # the services will not affect the compilation except, at worst, to slow it |
|
351 # down if stopping the antivirus fails. |
|
352 sub ParseOutput($) |
|
353 { |
|
354 my $iOutputRef = shift; |
|
355 my $iLogFile = shift; |
|
356 |
|
357 my $fh = \*LOGFILE; |
|
358 |
|
359 if($iLogFile) |
|
360 { |
|
361 unless (open $fh, ">>$iLogFile.log") |
|
362 { |
|
363 print ("WARNING: Failed to open log file: $iLogFile"); |
|
364 return 0; # FALSE = Failure |
|
365 } |
|
366 } |
|
367 else |
|
368 { |
|
369 $fh = \*STDOUT; |
|
370 } |
|
371 |
|
372 for my $line(@$iOutputRef) # For each line of output... |
|
373 { |
|
374 $line =~ s/\s+$//; # Remove trailing spaces (McAfee CSSCAN pads each line to 1024 chars!!) |
|
375 # Does it match an error as returned by PSKill or net stop/start |
|
376 if($line =~ /The specified service does not exist as an installed service\./i or |
|
377 $line =~ /The .*? service is not started\./i or |
|
378 $line =~ /The requested service has already been started./i or |
|
379 $line =~ /The service name is invalid\./i or |
|
380 $line =~ /Process does not exist\./i or |
|
381 $line =~ /Unable to kill process/i or |
|
382 $line =~ /error/i or |
|
383 $line =~ /is not recognized as an internal or external command/i) |
|
384 { |
|
385 print "WARNING: $line\n"; |
|
386 } |
|
387 else # No errors/warnings so just print the line on its own |
|
388 { |
|
389 print $fh "$line\n"; |
|
390 } |
|
391 } |
|
392 return 1; # TRUE = Success |
|
393 } |
|
394 |
|
395 # ExecuteProcess: |
|
396 # Description: Executes specified command using Perl module Win32::Process |
|
397 # Arguments: the command to execute as a hashref (filename of executable and parameters/args to pass to it) |
|
398 # Returns: none |
|
399 sub ExecuteProcess |
|
400 { |
|
401 my $iWaitSecs = shift; |
|
402 my $iCmd = shift; # Hashref - Command spec. |
|
403 |
|
404 my $iExecutable = $iCmd->{'AppName'}; |
|
405 unless(-e $iExecutable) |
|
406 { |
|
407 $iExecutable = FindFileFromPath($iCmd->{'AppName'}); |
|
408 unless ($iExecutable) |
|
409 { |
|
410 print "REMARK: File not found $iCmd->{'AppName'}\n"; |
|
411 return; |
|
412 } |
|
413 } |
|
414 my $iParams = $iCmd->{'Params'}; |
|
415 |
|
416 # my $iFlags = $iDebug? CREATE_NEW_CONSOLE: DETACHED_PROCESS; |
|
417 # my $iFlags = $iDebug? 0: DETACHED_PROCESS; |
|
418 my $iFlags = 0; |
|
419 |
|
420 # Create a new Perl process (because fork does not work on some versions of Perl on Win32) |
|
421 # $^X is the path to the Perl binary used to process this script |
|
422 my $iProcess; |
|
423 unless (Win32::Process::Create($iProcess, "$iExecutable", "\"$iExecutable\" $iParams", 0, $iFlags, ".")) |
|
424 { |
|
425 print "WARNING: Failed to create process for $iExecutable $iParams.\n"; |
|
426 my $iExitCode = Win32::GetLastError(); |
|
427 ReportProcessError($iExitCode); |
|
428 return; |
|
429 } |
|
430 |
|
431 my $iPID = $iProcess->GetProcessID(); |
|
432 print "\nExecuting: $iExecutable $iParams .....\n"; |
|
433 my $iRetVal = $iProcess->Wait($iWaitSecs * 1000); # milliseconds. Return value is zero on timeout, else 1. |
|
434 if ($iRetVal == 0) # Wait timed out |
|
435 { # No error from child process (so far) |
|
436 print "Spawned: $iExecutable $iParams - PID=$iPID.\n"; |
|
437 return 1; # Success |
|
438 } |
|
439 else # Child process terminated. Wait usually returns 1. |
|
440 { # Error in child process?? If so, get exit code |
|
441 my $iExitCode; |
|
442 $iProcess->GetExitCode($iExitCode); |
|
443 unless($iExitCode) |
|
444 { |
|
445 print "Executed: $iExecutable $iParams - PID=$iPID.\n"; |
|
446 return 1; # Success |
|
447 } |
|
448 print "REMARK: Failed in execution of $iExecutable $iParams.\n"; |
|
449 ReportProcessError($iExitCode); |
|
450 return 0; |
|
451 } |
|
452 } |
|
453 |
|
454 # ReportProcessError: |
|
455 # Description: prints error code to STDOUT followed by explanatory text, if avalable |
|
456 # Arguments: Windows error code |
|
457 # Returns: none |
|
458 sub ReportProcessError |
|
459 { |
|
460 my $iExitCode = shift; |
|
461 |
|
462 my $iMsg = Win32::FormatMessage($iExitCode); |
|
463 if (defined $iMsg) |
|
464 { |
|
465 printf "ExitCode: 0x%04x = %s\n", $iExitCode, $iMsg; |
|
466 } |
|
467 else |
|
468 { |
|
469 printf "ExitCode: 0x%04x\n", $iExitCode; |
|
470 } |
|
471 } |
|
472 |
|
473 # FindFileFromPath: |
|
474 # Description: Tries to find a file using PATH Environment Variable |
|
475 # Argument: Filename (Must include extension .EXE, .CMD etc) |
|
476 # Return: Full pathname of file, if found, otherwise undef |
|
477 sub FindFileFromPath |
|
478 { |
|
479 my $iFilename = shift; |
|
480 my $iPathname; |
|
481 my @iPathDirs = split /;/, $ENV{'PATH'}; |
|
482 foreach my $iDir (@iPathDirs) |
|
483 { |
|
484 while ($iDir =~ s/\\$//){;} # Remove any trailing backslash(es) |
|
485 $iPathname = "$iDir\\$iFilename"; |
|
486 if (-e $iPathname) |
|
487 { |
|
488 return $iPathname; |
|
489 } |
|
490 } |
|
491 return undef; |
|
492 } |
|
493 |
|
494 # RemoveExistingFile: |
|
495 # Description: If specified .LOG file exists, rename to .BAK |
|
496 # Arguments: File to check |
|
497 # Returns: TRUE on Success |
|
498 sub RemoveExistingFile |
|
499 { |
|
500 my $iFilename = shift; # Full pathname LESS extension |
|
501 |
|
502 unless (-e "$iFilename.log") { return 1; } # Success! |
|
503 |
|
504 if (File::Copy::move("$iFilename.log","$iFilename.bak")) { return 1; } # Success! |
|
505 |
|
506 print "WARNING: Failed to rename $iFilename.log\nto $iFilename.bak because of:\n$!\n"; |
|
507 return 0; # FALSE = Failure |
|
508 } |
|
509 |
|
510 1; |
|