/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004, 2005, 2006], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. This program is distributed
* in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.util.file.match;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.tools.ant.types.selectors.FileSelector;
import org.hyperic.sigar.FileSystem;
import org.hyperic.sigar.Sigar;
import org.hyperic.sigar.SigarException;
/**
* Implements a powerful file/directory matcher on top of Ant's fileset
* classes.
*/
public class Matcher {
/**
* Construct an Matcher.
*/
public Matcher () {}
/**
* Sets up the appropriate MatcherScanners to use for the search.
* @param config The config to use to initialize
* @return A List of MatcherScanners to use for the search.
*/
private List initScanners (MatcherConfig config,
FileSystem[] filesystems,
MatchResults results) {
List dirs = config.getSearchDirs();
String dir;
int i, j;
MatcherScanner scanner;
List scanners = new ArrayList();
List selectorList = config.getMatchSelectors();
MatchSelector[] selectors;
MasterMatchSelector masterSelector;
FileSelector[] masterSelectorArray;
List excludes = config.getExcludePatterns();
Log log = config.getLog();
String[] excludesArray;
selectors = new MatchSelector[selectorList.size()];
for ( i=0; i<selectors.length; i++ ) {
selectors[i] = (MatchSelector) selectorList.get(i);
}
masterSelectorArray = new FileSelector[1];
masterSelectorArray[0]
= new MasterMatchSelector(selectors,
config.getAllowMultipleMatches());
excludesArray = new String[excludes.size()];
for ( i=0; i<excludesArray.length; i++ ) {
excludesArray[i] = excludes.get(i).toString();
}
for ( i=0; i<dirs.size(); i++ ) {
dir = (String) dirs.get(i);
if ( shouldSkip(dir, log, results) ) continue;
if ( config.getFSTypes() != MatcherConfig.FS_ALL ) {
if ( !isFSTypeMatch(dir,
config.getFSTypes(),
filesystems,
log) ) continue;
}
scanner = new MatcherScanner();
scanner.setLog(log);
scanner.setBasedir(new File(dir));
scanner.setSelectors(masterSelectorArray);
scanner.setExcludes(excludesArray);
scanner.setFollowSymlinks(config.getFollowSymlinks());
scanner.setMatcherInterruptCB(config.getMatcherInterruptCB());
scanner.setMatcherProgressCB(config.getMatcherProgressCB());
scanner.setMaxDepth(config.getMaxDepth());
scanners.add(scanner);
}
return scanners;
}
/**
* Determine if the dir is on a filesystem that we should
* be searching.
* @param dir The name of the directory.
* @param fstypes The filesystem types we're supposed to search.
* @param filesystems The filesystems that sigar detected.
* @param log The log to use for errors and warnings.
* @return true if the directory should be included in the search,
* false otherwise.
*/
private boolean isFSTypeMatch ( String dir,
int fstypes,
FileSystem[] filesystems,
Log log ) {
String longestMatch = "";
int longestMatchIndex = -1;
String absPath = (new File(dir)).getAbsolutePath();
String fsMountPoint;
if ( filesystems == null ) {
if ( log != null ) {
log.error("matcher: filesystems array was never initialized.");
}
throw new IllegalArgumentException("filesystems array was never "
+ "initialized.");
}
for ( int i=0; i<filesystems.length; i++ ) {
fsMountPoint = filesystems[i].getDirName();
if ( absPath.startsWith(fsMountPoint) &&
fsMountPoint.length() > longestMatch.length() ) {
longestMatch = fsMountPoint;
longestMatchIndex = i;
}
}
if ( longestMatchIndex == -1 ) {
if ( log != null ) {
log.warn("Directory " + dir + " did not match "
+ "any filesystems, it will not be searched.");
}
return false;
}
return isCandidateFS(fstypes,
filesystems[longestMatchIndex],
log);
}
/**
* Determine if the given filesystem is OK to search, based on
* the value of fstype which tells us what filesystem types we
* are supposed to search.
*/
public boolean isCandidateFS ( int fstype, FileSystem fs, Log log ) {
switch ( fs.getType() ) {
case FileSystem.TYPE_UNKNOWN:
if ( log != null ) {
log.warn("Encountered UNKNOWN filesystem (device="
+ fs.getDevName() + "): " + fs.getDirName());
}
return false;
case FileSystem.TYPE_NONE:
if ( log != null ) {
log.warn("Encountered NONE filesystem (device="
+ fs.getDevName() + "): " + fs.getDirName());
}
return false;
case FileSystem.TYPE_LOCAL_DISK:
return (fstype == MatcherConfig.FS_ALL ||
fstype == MatcherConfig.FS_LOCAL);
case FileSystem.TYPE_NETWORK:
return (fstype == MatcherConfig.FS_ALL ||
fstype == MatcherConfig.FS_NETWORK);
case FileSystem.TYPE_RAM_DISK:
case FileSystem.TYPE_CDROM:
case FileSystem.TYPE_SWAP:
return false;
default:
if ( log != null ) {
log.warn("Encountered filesystem with invalid type ("
+ fs.getType() + ") (device="
+ fs.getDevName() + "): "
+ fs.getDirName());
}
return false;
}
}
private FileSystem[] loadFilesystems (MatcherConfig config)
throws SigarException {
Log log = config.getLog();
FileSystem[] filesystems;
if ( config.getFSTypes() != MatcherConfig.FS_ALL ) {
Sigar sigar = new Sigar();
try {
filesystems = sigar.getFileSystemList();
return filesystems;
} catch ( SigarException se ) {
if ( log != null ) {
log.error("matcher: error getting "
+ "available filesystems:" + se, se);
}
throw se;
} finally {
sigar.close();
}
}
return null;
}
/**
* Get the matches for this search.
* @return A Map representing the matches. The keys in the Map are
* the keys that each MatchSelector in the MatcherConfig was
* initialized with in its constructor. The values are Lists, where
* each element in the List is a String representing the full path
* of the matched path.
* @exception MatcherInterruptedException If the search was interrupted
* before it could be completed. In this case, you can get the matches
* so far by calling getMatchesSoFar on the MatcherInterruptedException
* object.
* @exception SigarException If an error occurs reading the available
* filesystems - this can only happen if the config's getFSTypes returns
* a value other than MatcherConfig.FS_ALL.
*/
public synchronized MatchResults getMatches ( MatcherConfig config )
throws MatcherInterruptedException, SigarException {
int i, j;
List scanners;
MatcherScanner scanner;
MatchResults results = new MatchResults();
File f;
Log log = config.getLog();
FileSystem[] filesystems = null;
filesystems = loadFilesystems(config);
scanners = initScanners(config, filesystems, results);
for ( i=0; i<scanners.size(); i++ ) {
scanner = (MatcherScanner) scanners.get(i);
scanner.initMatches(results.matches);
try {
scanner.doScan();
} catch ( MatcherInterruptedException mie ) {
mie.setMatchesSoFar(scanner.getMatches());
if ( log != null ) {
log.warn("matcher: search interrupted.");
}
throw mie;
} catch ( Exception e ) {
// huh?
scanner.addError(new MatcherException("matcher: search error", e));
if ( log != null ) {
log.error("matcher: search error: " + e, e);
}
}
results.matches = scanner.getMatches();
if (log != null) {
log.debug("results.matches=" + results.matches);
}
results.errors.addAll(scanner.getErrors());
}
return results;
}
/**
* Should we skip a directory completely?
* @return true if dir is null, if dir is not a directory,
* if we can't read dir, or if dir does not exist.
*/
private boolean shouldSkip ( String dir, Log log, MatchResults results ) {
File dirFile;
String msg;
// Should never happen
if ( dir == null ) {
msg = "matcher: ignoring null dir.";
if ( log != null ) {
log.info(msg);
}
results.errors.add(new MatcherException(msg));
return true;
}
dirFile = new File(dir);
if ( !dirFile.exists() ) {
msg = "matcher: ignoring non-existent dir: " +
dirFile.getAbsolutePath();
if ( log != null ) {
log.info(msg);
}
results.errors.add(new MatcherException(msg));
return true;
}
if ( !dirFile.canRead() ) {
msg = "matcher: ignoring unreadable dir: " +
dirFile.getAbsolutePath();
if ( log != null ) {
log.info(msg);
}
results.errors.add(new MatcherException(msg));
return true;
}
if ( !dirFile.isDirectory() ) {
msg = "matcher: ignoring non-dir: " +
dirFile.getAbsolutePath();
if ( log != null ) {
log.info(msg);
}
results.errors.add(new MatcherException(msg));
return true;
}
return false;
}
}