public class FileHogCheck {
private final static String PACKAGE = "com.mplify.win2k.diskmon";
private final static String CLASS = PACKAGE + ".FileHogCheck";
private static final int ERROR_STATUS = 1; // Status for exit() calls in case of error
private static final int SUCCESS_STATUS = 0; // Status for exit() calls in case of success
private static final String ARGV_JNIDEBUG = "JNIDEBUG"; // argv keyword
private static final String ARGV_OUTFILE = "OUTFILE"; // argv keyword
private static final String ARGV_PROPERTIES = "PROPERTIES"; // argv keyword
private static final String ARGV_OVERWRITE = "OVERWRITE"; // argv keyword
private static final String ARGV_DEBUG = "DEBUG"; // argv keyword
private static final String PROPERTIES_KEY_TREEPOINTSET = "TREEPOINTSET"; // properties keyword
private static final String PROPERTIES_KEY_JNIDEBUG = "JNIDEBUG"; // properties keyword
private JNILayer jniLayer = new JNILayer(); // the interface to the Win32 subsystem
private Hashtable result = new Hashtable(); // this hashtable will be filled with HashKey -> HashValue mappings
private double now = (double) (System.currentTimeMillis()); // this is the 'now' moment, precomputed
private final static String[] TRAILER =
{
"+-------------------------------------------------------------------+",
"|This hamster checks a filesystem and lists the disk space occupied |",
"|by users on Win32 machines in an Microsoft Excel format, generated |",
"|with Jakarta POI. |",
"|Info is obtained by Win32 API calls through Java Native Interface. |",
"|(c) m-plify S.A. 2003; 47, av de la Liberté, L-1931 Luxembourg |",
"|Tél: +352 261846-1, Fax: +352 261846-46, Mail: support@m-plify.com |",
"+-------------------------------------------------------------------+" };
/**
* ------------------------------------------------------------------------------
* Locally used Exception, static so that it is not linked to the enclosing class
* ------------------------------------------------------------------------------
*/
public static class ExtremePrejudiceException extends Exception {
public ExtremePrejudiceException() {
super();
}
public ExtremePrejudiceException(String s) {
super(s);
}
}
/**
* ------------------------------------------------------------------------------
* This is just a container for data extracted during 'processArgv()', and
* an instance of the same is used there.
* ------------------------------------------------------------------------------
*/
private static class ArgvResult {
public Boolean use_jnidebug; // debug the JNI interface, set in static processArgv(), after which it is not null
public Boolean use_debug; // debug, set in static processArgv(), after which it is not null
public Boolean use_overwrite; // overwrite any existing Excel file if present
public String use_outfile; // name of the Excel output file, or (null)
public Vector use_treePoints; // file system tree points to use
}
/**
* ------------------------------------------------------------------------------
* This is just a container for a treepoint, i.e. a tuple
* ("name of directory or logical drive","name of machine holding security info or null")
* ------------------------------------------------------------------------------
*/
private static class TreePoint {
public String directory; // directory where to start the verification
public String machine; // machine to interrogate for sec.info. (null) means local machine
public TreePoint(String directory, String machine) {
this.directory = directory;
this.machine = machine;
}
}
/**
* ------------------------------------------------------------------------------
* This is just a container for a key in the result hashtable, the key
* contains the username + domain
* ------------------------------------------------------------------------------
*/
private static class HashKey {
private String user;
private String machine;
public HashKey(String user, String machine) {
if (user == null) {
throw new IllegalArgumentException("The passed 'user' is (null)");
}
if (machine == null) {
throw new IllegalArgumentException("The passed 'machine' is (null)");
}
this.user = user;
this.machine = machine;
}
public String getUser() {
return user;
}
public String getMachine() {
return machine;
}
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof HashKey))
return false;
HashKey other = (HashKey) obj;
return (user.equals(other.user) && machine.equals(other.machine));
}
public int hashCode() {
return user.hashCode() ^ machine.hashCode();
}
}
/**
* ------------------------------------------------------------------------------
* This is just a container for a value in the result hashtable, the value
* contains the number of files and the total size
* ------------------------------------------------------------------------------
*/
private static class HashValue {
private int fileCount;
private int fileCount_compressed;
private int fileCount_encrypted;
private int fileCount_tmp;
private int fileCount_os;
private int fileCount_hidden;
private long totalSize;
private double weightedDaysSum;
public HashValue(int fileCount, long fileSize) {
this.fileCount = fileCount;
this.totalSize = fileSize;
this.weightedDaysSum = 0;
this.fileCount_compressed = 0;
this.fileCount_encrypted = 0;
this.fileCount_tmp = 0;
this.fileCount_os = 0;
this.fileCount_hidden = 0;
}
public void addToWeightedDaysSum(double x) {
weightedDaysSum += x;
}
public void addToTotalSize(long x) {
totalSize += x;
}
public void addToFileCount(int x) {
fileCount += x;
}
public int getFileCount() {
return fileCount;
}
public int getCompressedFileCount() {
return fileCount_compressed;
}
public int getEncryptedFileCount() {
return fileCount_encrypted;
}
public int getHiddenFileCount() {
return fileCount_hidden;
}
public int getOSFileCount() {
return fileCount_os;
}
public int getTmpFileCount() {
return fileCount_tmp;
}
public long getTotalSize() {
return totalSize;
}
public double getWeightedDaysSum() {
return weightedDaysSum;
}
public void addToCompressedFileCount(int x) {
fileCount_compressed += x;
}
public void addToEncryptedFileCount(int x) {
fileCount_encrypted += x;
}
public void addToHiddenFileCount(int x) {
fileCount_hidden += x;
}
public void addToOSFileCount(int x) {
fileCount_os += x;
}
public void addToTmpFileCount(int x) {
fileCount_tmp += x;
}
}
/**
* Recursively examine a treepoint that we (ultimately) got from the properties file
*/
private void examineTreePoint(TreePoint tp, boolean debug) {
Logger logCat = LOGGER_examineTreePoint;
logCat.info("Examining treepoint ('" + tp.directory + "','" + tp.machine + "')");
//
// Now it's getting windows-specific: The treepoint name may indicate a file or
// directory on the local disk, on a remote disk, on a logical volume; it may
// also be a machine name in UNC (e.g. \\Blueshoes for a machine or \\Blueshoes\smbdato
// for a share or M: for a disk or M:\whatever for a file). We try to distinguish the
// case where the treepoint is just a UNC machine name (\\Blueshoes) as that case
// is special in the sense that we have to list the disk resources reachable for that
// machine before we are able to traverse any filesystem
//
if (looksLikeHostInUNC(tp.directory)) {
logCat.info("The following string has been recognized as a hostname in UNC: \"" + tp.directory + "\"");
//
// do a network enumeration on that presumed UNC hostname; we do not need to do recursive
// examination on the host, just list the drives directly underneath that 'node'
//
JNILayer layer = new JNILayer();
final int TYPE = Win32NetResource.TYPE_DISK; // we want disks (as opposed to print resources)
final int USAGE = Win32NetResource.USAGE_ALL; // any usage is acceptable
//
// make sure the string contains those silly backward slashes only (whoever thought
// that using an escape character as a tree node name separator would ever become
// accepted practice?)
//
String uncHost = tp.directory.replace('/', '\\');
JNILayer$Win32GetNetResources queryResult = layer.getWin32GetNetResources(TYPE, USAGE, uncHost, LOGGER_JNI.isDebugEnabled());
//
// the 'queryResult' should now contain a full list of the disks available on that machine
//
if (queryResult.getReturnValue()) {
// success, the queryResult contains a list of NetResources that can be examined
for (int j = 0; j < queryResult.getResultSize(); j++) {
Win32NetResource currentResource = queryResult.getResult(j);
if (logCat.isDebugEnabled()) {
// do some in-line prettyprinting
StringTokenizer tk = new StringTokenizer(currentResource.toString(), "\n");
while (tk.hasMoreTokens()) {
logCat.debug(tk.nextToken());
}
}
if (!currentResource.looksLikeInnerNode()) {
logCat.debug("The following network directory will be examined: \"" + currentResource.getRemoteName() + "\"");
File current = new File(currentResource.getRemoteName());
if (!current.exists()) {
logCat.error("'" + current + "' does not exist");
} else if (!current.canRead()) {
logCat.error("Can't read '" + current + "'");
return;
} else {
examineRecursively(current, tp.machine, 0);
}
} else {
logCat.error("There seems to be an inner node in the result set, named \"" + currentResource.getRemoteName() + "\"; skipped it!!");
}
}
} else {
// failure, let's see what happened
logCat.error("-- Network Query Failure for machine \"" + tp.directory + "\" --");
if (queryResult.getMyError() != 0) {
// myError is never null and does not need trimming
logCat.error("-- MyError==" + queryResult.getMyError() + ", " + queryResult.getMyErrorString());
}
if (queryResult.getLastError() != 0) {
String msg = queryResult.getLastErrorString();
if (msg != null) {
msg = msg.trim();
}
logCat.error("-- LastError==" + queryResult.getLastError() + ", " + msg);
}
if (queryResult.getLastNetError() != 0) {
String msg = queryResult.getLastNetErrorString();
if (msg != null) {
msg = msg.trim();
}
logCat.error("-- LastNetError==" + queryResult.getLastNetError() + ", " + msg);
}
}
} else {
logCat.info("The following string rather looks like a file or directory: \"" + tp.directory + "\"");
//
// make sure the string contains those silly backward slashes only (whoever thought
//
String uncHost = tp.directory.replace('/', '\\');
File current = new File(tp.directory);
if (!current.exists()) {
logCat.error("'" + tp.directory + "' does not exist");
} else if (!current.canRead()) {
logCat.error("Can't read '" + tp.directory + "'");
return;
} else {
examineRecursively(current, tp.machine, 0);
}
}
}
/**
* Print the 'result' hash to the Excel97 file with the given name
*/
private void excelizeResult(String outfile) throws Exception {
final String[] COLUMN_NAMES = { "User%Domain", "File count", "Total size", "Weighted age (days)", "Compressed files", "Hidden files", "OS files", "Encrypted files", "Temporary files" };
//
// first the workbook and a new sheet
//
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet(new SimpleDateFormat("yyyy.MM.dd").format(new java.util.Date()));
//
// need an array takes up the max number of characters over any cell in a row, so that we
// may adjust the row widths in the end
//
int[] maxCharsInCol = new int[COLUMN_NAMES.length];
//
// need two cell styles to format numerics, and one for the header
//
HSSFCellStyle styleWithDots = wb.createCellStyle();
styleWithDots.setDataFormat(HSSFDataFormat.getFormat("#,##0")); // retrieve a builtin format
HSSFCellStyle styleWithDecimals = wb.createCellStyle();
styleWithDecimals.setDataFormat(HSSFDataFormat.getFormat("0.00")); // retrieve a builtin format
HSSFCellStyle styleForHeader = wb.createCellStyle();
// setting the colour etc: could not get it to work properly
// styleForHeader.setFillBackgroundColor(HSSFColorConstants.BLUE);
// styleForHeader.setFillPattern((short)1); // someone forgot the FILL constant
//
// create the header row
//
{
final short rowIndex = 0;
HSSFRow row = sheet.createRow(rowIndex);
for (short colIndex = 0; colIndex < COLUMN_NAMES.length; colIndex++) {
HSSFCell c = row.createCell(colIndex);
c.setCellValue(COLUMN_NAMES[colIndex]);
c.setCellStyle(styleForHeader);
maxCharsInCol[colIndex] = COLUMN_NAMES[colIndex].length();
}
}
//
// loop over result, creating rows of data, starting at row 1
//
{
Enumeration enum = result.keys();
short rowIndex = 1;
while (enum.hasMoreElements()) {
HashKey hk = (HashKey) (enum.nextElement());
HashValue hv = (HashValue) (result.get(hk));
HSSFRow row = sheet.createRow(rowIndex++);
{
final short colIndex = 0;
// row 0 contains "user%domain"; the cell will go to string type on setCellValue()
HSSFCell c = row.createCell(colIndex);
String value = hk.getUser() + "%" + hk.getMachine();
c.setCellValue(value);
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], value.length());
}
{
final short colIndex = 1;
// contains file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getFileCount()).length());
}
{
final short colIndex = 2;
// contains total file size; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
double value = hv.getTotalSize();
c.setCellValue(value);
// set up a format that shows some formatting (maybe this should be a common cell style?)
c.setCellStyle(styleWithDots);
// same thing in Java to determine the length of the result
{
DecimalFormat df = new DecimalFormat("#,##0");
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], df.format(value).length());
}
}
{
final short colIndex = 3;
// contains weighted average in days; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
double value = 0;
if (hv.getTotalSize() != 0) {
value = hv.getWeightedDaysSum() / ((double) hv.getTotalSize());
}
c.setCellValue(value);
// set up a format that shows some formatting (maybe this should be a common cell style?)
c.setCellStyle(styleWithDecimals);
// same thing in Java to determine the length of the result
{
DecimalFormat df = new DecimalFormat("0.00");
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], df.format(value).length());
}
}
{
final short colIndex = 4;
// contains compressed file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getCompressedFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getCompressedFileCount()).length());
}
{
final short colIndex = 5;
// contains hidden file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getHiddenFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getHiddenFileCount()).length());
}
{
final short colIndex = 6;
// contains operating system file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getOSFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getOSFileCount()).length());
}
{
final short colIndex = 7;
// contains encrypted file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getEncryptedFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getEncryptedFileCount()).length());
}
{
final short colIndex = 8;
// contains temporary file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getTmpFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getTmpFileCount()).length());
}
}
}
//
// loop over columns, to set an agreeable width, using the maxes collected
// the width is given in units of 1/256th of a character width
//
for (short colIndex = 0; colIndex < COLUMN_NAMES.length; colIndex++) {
final int ADD_CHARS = 5;
sheet.setColumnWidth(colIndex, (short) ((maxCharsInCol[colIndex] + ADD_CHARS) * 256));
}
OutputStream os = new BufferedOutputStream(new FileOutputStream(outfile));
wb.write(os);
os.close();
}
/**
* Generate a key for the properties file examination
*/
private static String generate_PROPERTIES_KEY_TREEPOINT_DIRECTORY(int x) {
return "TREEPOINT." + x + ".DIRECTORY";
}
/**
* Generate a key for the properties file examination
*/
private static String generate_PROPERTIES_KEY_TREEPOINT_MACHINE(int x) {
return "TREEPOINT." + x + ".MACHINE";
}
/**
* main()
*/
public static void main(String[] argv) {
Logger logCat = LOGGER_main;
//
// prepare logging, set the default level to INFO
//
Layout layout = new SimpleLayout();
Logger.getRoot().addAppender(new ConsoleAppender(layout, ConsoleAppender.SYSTEM_OUT));
Logger.getRoot().setLevel(Level.INFO);
//
// read from the commandline
//
ArgvResult argvResult = null;
try {
if (argv == null) {
argv = new String[0];
}
if (argv.length == 1 && ("HELP".equalsIgnoreCase(argv[0]) || "-h".equalsIgnoreCase(argv[0]) || "--help".equalsIgnoreCase(argv[0]) || "/h".equalsIgnoreCase(argv[0]))) {
printUsage();
System.exit(SUCCESS_STATUS);
} else {
argvResult = processArgv(argv);
}
} catch (Exception exe) {
logCat.error("While handling command-line arguments", exe);
System.exit(ERROR_STATUS);
}
//
// maybe set the log4j level to DEBUG, depending on what was given on the command line
// The 'JNI' logger determines how the JNI code will act
//
if (argvResult.use_debug.booleanValue()) {
Logger.getRoot().setLevel(Level.DEBUG);
}
if (argvResult.use_jnidebug.booleanValue()) {
Logger.getLogger("JNI").setLevel(Level.DEBUG);
} else {
Logger.getLogger("JNI").setLevel(Level.INFO);
}
//
// get the 'FileHogCheck', using values possibly set through the commandline
// Note that we use the same file for temporary results as for the final result
//
FileHogCheck fhc = new FileHogCheck(argvResult,argvResult.use_outfile);
//
// on return the 'fhc' contains data, which we now output
//
boolean res = fhc.printCurrentResults(argvResult.use_outfile);
if (res) {
System.exit(SUCCESS_STATUS);
}
else {
System.exit(ERROR_STATUS);
}
}
/**
* Parse the passed 'value' into a boolean - many values are acceptable but if none of these
* fits, an exception is thrown.
*/
private static boolean parseBoolean(String value) {
String lvalue = value.toLowerCase(); // trimming already done
if (lvalue.equals("yes") || lvalue.equals("true") || lvalue.equals("on") || lvalue.equals("1") || lvalue.equals("y") || lvalue.equals("t")) {
return true;
}
if (lvalue.equals("no") || lvalue.equals("false") || lvalue.equals("off") || lvalue.equals("0") || lvalue.equals("n") || lvalue.equals("f")) {
return false;
}
throw new IllegalArgumentException("The passed value is not a boolean");
}
/**
* Parse the line containing the list of comma-separated atoms
* This list has been obtained from a 'properties' file and its String value
* is in the passed 'matchThis'. The key name is also passed as 'keyName'
* in order to print good debugging information.
*/
private static HashSet parseCommaSeparatedAtoms(String matchThis, String keyName) {
HashSet set = new HashSet();
//
// Parse using ORO
//
Perl5Compiler compiler = new Perl5Compiler();
Perl5Matcher matcher = new Perl5Matcher();
Pattern patternFront = null;
Pattern patternBack = null;
try {
patternFront = compiler.compile("^([\\w\\-]+)(.*)"); // we are looking for things that start like this
patternBack = compiler.compile("^\\s*,\\s*([\\w\\-]+)(.*)"); // and continue like this
} catch (MalformedPatternException exe) {
}
if (!matcher.contains(matchThis, patternFront)) {
throw new IllegalStateException("The value for the key \"" + keyName + "\" could not be parsed, it is not a comma-separated list of [A-Za-z0-9_-]");
} else {
// matcher has already the first result in its guts
MatchResult result = matcher.getMatch();
// group '0' is the entire match, we are interested in the first value, i.e. group 1
set.add(result.group(1));
matchThis = result.group(2);
while (matcher.contains(matchThis, patternBack)) {
result = matcher.getMatch();
set.add(result.group(1));
matchThis = result.group(2);
}
// verify whether that is all
if (matchThis.trim().length() != 0) {
throw new IllegalStateException("The value for the key \"" + keyName + "\" has some unparseable stuff at the end: \"" + matchThis + "\"");
}
}
return set;
}
/**
* Print the 'result' hash in a primitive fashion
*/
public void printResult() {
Enumeration enum = result.keys();
while (enum.hasMoreElements()) {
HashKey hk = (HashKey) (enum.nextElement());
System.out.println("User '" + hk.getUser() + "' on machine '" + hk.getMachine() + "'");
HashValue hv = (HashValue) (result.get(hk));
{
double avg = 0;
if (hv.getTotalSize() != 0) {
avg = hv.getWeightedDaysSum() / ((double) hv.getTotalSize());
}
DecimalFormat df = new DecimalFormat(".00");
System.out.println(" Total files...........: " + hv.getFileCount() + ", size=" + hv.getTotalSize() + " byte, avg=" + df.format(avg) + " days");
}
if (hv.getCompressedFileCount() > 0) {
System.out.println(" Compressed files......: " + hv.getCompressedFileCount());
}
if (hv.getHiddenFileCount() > 0) {
System.out.println(" Hidden files..........: " + hv.getHiddenFileCount());
}
if (hv.getOSFileCount() > 0) {
System.out.println(" Operating system files: " + hv.getOSFileCount());
}
if (hv.getEncryptedFileCount() > 0) {
System.out.println(" Encrypted files.......: " + hv.getEncryptedFileCount());
}
if (hv.getTmpFileCount() > 0) {
System.out.println(" Temporary files.......: " + hv.getTmpFileCount());
}
}
}
/**
* Print usage definition to stderr
*/
private static void printUsage() {
for (int i = 0; i < TRAILER.length; i++) {
System.err.println(TRAILER[i]);
}
System.err.println("USAGE: Call " + CLASS + " with the following keywords and arguments:");
System.err.println(" " + ARGV_JNIDEBUG.toUpperCase());
System.err.println(" if present, the C part of the program prints out debug info");
System.err.println(" " + ARGV_PROPERTIES.toUpperCase() + " <value>");
System.err.println(" where <value> is the name of a file holding properties");
System.err.println(" " + ARGV_OUTFILE.toUpperCase() + " <value>");
System.err.println(" where <value> is the name of an Excel97 file that will be written");
System.err.println(" (result goes to stdout if that argument is not present)");
System.err.println(" " + ARGV_OVERWRITE.toUpperCase());
System.err.println(" if present, the Excel97 will be overwritten if it exists");
System.err.println(" " + ARGV_DEBUG.toUpperCase());
System.err.println(" if present, the log level will do to debug");
}
/**
* Process the argv passed to main(). Throws an exception if there is a problem (some exceptions
* are caught). On problem, a message is written to 'stderr'. Some stuff is logged using log4j
*/
private static ArgvResult processArgv(String[] argv) throws Exception {
Logger logCat = LOGGER_processArgv;
String local_properties = null; // no properties have been registered
String local_outfile = null; // no output file has been registered
Boolean processed_jnidebug = Boolean.FALSE; // switch-on flag is off
Boolean processed_overwrite = Boolean.FALSE; // switch-on flag is off
Boolean processed_debug = Boolean.FALSE; // switch-on flag is off
ArgvResult result = new ArgvResult();
//
// process command line
//
{
String expect = null; // what value to expect next
for (int i = 0; i < argv.length; i++) {
if (expect == null) {
// state: expecting a new keyword
// get a new keyword (which may be the start of a new keyword-value pair)
if (argv[i].equalsIgnoreCase(ARGV_JNIDEBUG)) {
processed_jnidebug = Boolean.TRUE;
expect = null;
} else if (argv[i].equalsIgnoreCase(ARGV_DEBUG)) {
processed_debug = Boolean.TRUE;
expect = null;
} else if (argv[i].equalsIgnoreCase(ARGV_OVERWRITE)) {
processed_overwrite = Boolean.TRUE;
expect = null;
} else if (argv[i].equalsIgnoreCase(ARGV_PROPERTIES)) {
expect = ARGV_PROPERTIES;
} else if (argv[i].equalsIgnoreCase(ARGV_OUTFILE)) {
expect = ARGV_OUTFILE;
} else {
printUsage();
System.err.println("******************");
System.err.println("ERROR: Unknown keyword \"" + argv[i] + "\" encountered");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
} else if (expect.equalsIgnoreCase(ARGV_PROPERTIES)) {
expect = null;
local_properties = argv[i];
} else if (expect.equalsIgnoreCase(ARGV_OUTFILE)) {
expect = null;
local_outfile = argv[i];
} else {
throw new IllegalStateException("Unhandled case of 'expect': \"" + expect + "\"");
}
}
}
//
// Postprocess
//
FileInputStream propFis = null;
if (local_properties != null) {
try {
propFis = new FileInputStream(new File(local_properties));
} catch (Exception exe) {
System.err.println("******************");
System.err.println("ERROR: Value for " + ARGV_PROPERTIES + " (" + local_properties + ") is an invalid or an inaccessible file");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
}
//
// Assign defaults
//
result.use_jnidebug = processed_jnidebug; // immediate
result.use_debug = processed_debug; // immediate
result.use_treePoints = new Vector(); // will be filled in, empty by default
result.use_outfile = local_outfile; // immediate
result.use_overwrite = processed_overwrite; // immediate
//
// Check whether the output file can be written and can be *over*written
//
if (result.use_outfile != null) {
File outfile = new File(result.use_outfile);
if (outfile.exists() && !result.use_overwrite.booleanValue()) {
System.err.println("******************");
System.err.println("Output file '" + outfile + "' exists and cannot be overwritten");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
if (outfile.exists() && !outfile.isFile()) {
System.err.println("******************");
System.err.println("Output file '" + outfile + "' is not a file; cannot overwrite");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
if (!outfile.exists()) {
// try to create the file
if (!outfile.createNewFile()) {
System.err.println("******************");
System.err.println("Output file '" + outfile + "' could not be created");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
}
}
//
// Read properties, if any
//
if (propFis != null) {
Properties props = new Properties();
props.load(propFis);
propFis.close();
//
// handle JNIDEBUG, i.e. set the jnidebug to true if the properties-value is true, leave it otherwise
//
if (props.containsKey(PROPERTIES_KEY_JNIDEBUG)) {
result.use_jnidebug = new Boolean(result.use_jnidebug.booleanValue() || parseBoolean(props.getProperty(PROPERTIES_KEY_JNIDEBUG)));
}
//
// handle the set of treepoints
//
{
Set treepointSet = new HashSet();
if (props.containsKey(PROPERTIES_KEY_TREEPOINTSET)) {
HashSet treePointSet = parseCommaSeparatedAtoms(props.getProperty(PROPERTIES_KEY_TREEPOINTSET), PROPERTIES_KEY_TREEPOINTSET);
Iterator iter = treePointSet.iterator();
while (iter.hasNext()) {
String id = (String) (iter.next());
int num;
try {
num = Integer.parseInt(id);
} catch (NumberFormatException exe) {
System.err.println("******************");
System.err.println("ERROR: Non-numeric id '" + id + "' found in treepoint-set list in properties file...skipped entry");
System.err.println("******************");
continue;
}
String directoryKey = generate_PROPERTIES_KEY_TREEPOINT_DIRECTORY(num);
String machineKey = generate_PROPERTIES_KEY_TREEPOINT_MACHINE(num);
if (!props.containsKey(machineKey)) {
// skip it
System.err.println("******************");
System.err.println("ERROR: No value for key '" + machineKey + "' found; check your properties file...skipped entry");
System.err.println("******************");
} else if (!props.containsKey(directoryKey)) {
// skip it
System.err.println("******************");
System.err.println("ERROR: No value for key '" + directoryKey + "' found; check your properties file...skipped entry");
System.err.println("******************");
} else {
String directory = props.getProperty(directoryKey);
String machine = props.getProperty(machineKey);
if (machine.equalsIgnoreCase("null") || machine.equalsIgnoreCase("(null)")) {
machine = null;
}
result.use_treePoints.add(new TreePoint(directory, machine));
logCat.info("Loaded new treepoint ('" + directory + "','" + machine + "')");
}
}
}
}
} else {
System.err.println("******************");
System.err.println("ERROR: You must specify a properties file with " + ARGV_PROPERTIES);
System.err.println("******************");
throw new ExtremePrejudiceException();
}
return result;
}
private final static Logger LOGGER_examineFile = Logger.getLogger(CLASS + ".examineFile"); // a logger
private final static Logger LOGGER_examineRecursively = Logger.getLogger(CLASS + ".examineRecursively"); // a logger
private final static Logger LOGGER_examineTreePoint = Logger.getLogger(CLASS + ".examineTreePoint"); // a logger
private final static Logger LOGGER_JNI = Logger.getLogger("JNI"); // logger for JNI
private final static Logger LOGGER_main = Logger.getLogger(CLASS + ".main"); // a logger
private final static Logger LOGGER_processArgv = Logger.getLogger(CLASS + ".processArgv"); // a logger
/**
* Examine something classified as a 'file'; the Win32 filename (in UNC format or not) is given
* and should (hopefully) be resolved by the JVM into something valid.
*/
private void examineFile(File current, String machine) {
filesHandled++;
if (checkpoint_countdown<=0) {
doCheckPointProcessing();
checkpoint_countdown=CHECKPOINT_COUNTDOWN;
}
else {
checkpoint_countdown--;
}
JNILayer$Win32GetFileInformation fileInfo = jniLayer.getWin32GetFileInformation(current.getAbsolutePath(), machine, LOGGER_JNI.isDebugEnabled());
if (fileInfo.getReturnValue()) {
// all went well, so add the result to the totals
HashKey hk = new HashKey(fileInfo.getAccountName(), fileInfo.getDomainName());
HashValue hv = null;
if (result.containsKey(hk)) {
hv = (HashValue) (result.get(hk));
hv.addToFileCount(1);
hv.addToTotalSize(fileInfo.getFileSize());
} else {
hv = new HashValue(1, fileInfo.getFileSize());
result.put(hk, hv);
}
// also add to the 'weighted creation time', which is the numerator of a weighted
// sum of (now-creationDate); weighs are the file sizes...
if (fileInfo.isFileCreationTimeOk()) {
long creation = fileInfo.getFileCreationTime().getTime();
double delta_days = (now - (double) creation) / (3600 * 24 * 1000);
hv.addToWeightedDaysSum(delta_days * fileInfo.getFileSize());
}
// also flag any special stuff
{
BitSet flags = fileInfo.getFileAttributeFlags();
if (flags.get(fileInfo.FILE_ATTRIBUTE_COMPRESSED)) {
hv.addToCompressedFileCount(1);
}
if (flags.get(fileInfo.FILE_ATTRIBUTE_ENCRYPTED)) {
hv.addToEncryptedFileCount(1);
}
if (flags.get(fileInfo.FILE_ATTRIBUTE_HIDDEN)) {
hv.addToHiddenFileCount(1);
}
if (flags.get(fileInfo.FILE_ATTRIBUTE_SYSTEM)) {
hv.addToOSFileCount(1);
}
if (flags.get(fileInfo.FILE_ATTRIBUTE_TEMPORARY)) {
hv.addToTmpFileCount(1);
}
}
} else {
// some error occurred
String msg = "Some error occurred for '" + current.getAbsolutePath() + "' with Win32GetFileInformation";
switch (fileInfo.getErrorWith()) {
case fileInfo.ERROR_WITH_FILE_TIME_TO_SYSTEM_TIME :
msg += " in FileTimeToSystemTime(); last error = " + fileInfo.getLastError();
break;
case fileInfo.ERROR_WITH_GET_FILE_ATTRIBUTES_EX :
msg += " in GetFileAttributesEx(); last error = " + fileInfo.getLastError();
break;
case fileInfo.ERROR_WITH_GET_NAMED_SECURITY_INFO :
msg += " in GetNamedSecurityInfo(); error code = " + fileInfo.getLastError();
break;
case fileInfo.ERROR_WITH_LOOKUP_ACCOUNT_SID :
msg += " in ErrorWithLookupAccountSid(); last code = " + fileInfo.getLastError();
break;
case fileInfo.ERROR_WITH_PARAMETERS :
msg += " -- bad parameters"; // no error code or last error here
break;
default :
msg += " in unknown procedure";
}
String ext = fileInfo.getLastErrorString();
if (ext != null) {
msg += ": \"";
msg += ext.trim();
msg += "\"";
}
LOGGER_examineFile.warn(msg);
}
}
/**
* Do a depth-first recursive examination of a file 'current' (which might be a file or
* a directory), with security information taken from 'machine'. The current depth of the
* examination is 'depth'; the Win32 filename (in UNC format or not) is given
* and should (hopefully) be resolved by the JVM into something valid.
*/
private void examineRecursively(File current, String machine, int depth) {
if (LOGGER_examineRecursively.isDebugEnabled()) {
LOGGER_examineRecursively.debug("Checking '" + current.getAbsolutePath() + "'");
}
if (current.isFile()) {
examineFile(current, machine);
} else if (current.isDirectory()) {
File[] files = current.listFiles();
if (files == null) {
LOGGER_examineRecursively.error("List of files obtained for '" + current.getAbsolutePath() + "' is (null); current depth is " + depth);
} else {
for (int i = 0; i < files.length; i++) {
examineRecursively(files[i], machine, depth + 1);
}
}
} else {
LOGGER_examineRecursively.warn("File '" + current.getAbsolutePath() + "' is neither a file nor a directory; skipped");
}
}
/**
* Check whether a string looks like a hostname in UNC (Microsoft Universal Naming Convention)
*/
private static boolean looksLikeHostInUNC(String str) {
if (str == null) {
return false;
}
str = str.trim();
if (str.length() < 3) {
return false;
}
if (str.charAt(0) != '\\' && str.charAt(0) != '/' && str.charAt(1) != '\\' && str.charAt(1) != '/') {
return false;
}
StringTokenizer st = new StringTokenizer(str, "\\/");
if (st.countTokens() > 1) {
return false;
}
return true;
}
private long checkpoint_countdown; // we do not want to do checkpoint processing at every file; this is a countdown
private static final long CHECKPOINT_COUNTDOWN = 20; // when will checkpoints processing be done? every 20 files
private static final long CHECKPOINT_DELTA = 1*60*1000; // when will checkpoints be?
private long checkpoint_next; // a time value: when will the next checkpoint be
private long checkpoint_start; // a time value: when did processing start
private int filesHandled = 0; // number of files already handled (0 at the start)
private String outputFile; // name of the file we will write Excel results at every checkpoint
/**
* Constructor runs the algorithm. Final Output is left to the caller.
* However, we temporarily write out the result Excel file. To do so, the
* filename of the Excel file is passed. If it is (null), nothing will be written.
* Fo flexibility, the name of the ouptut file in ArgvResult is NOT used.
*/
public FileHogCheck(ArgvResult argv,String outputFile) {
Logger logCat = Logger.getLogger(CLASS + ".FileHogCheck");
//
// Initialize checkpoint time accounting
//
checkpoint_start = System.currentTimeMillis();
checkpoint_next = checkpoint_start + CHECKPOINT_DELTA;
checkpoint_countdown = CHECKPOINT_COUNTDOWN;
//
// set up the intermediate result file
//
this.outputFile = outputFile;
//
// loop over all the treepoints
//
{
Enumeration enum = argv.use_treePoints.elements();
while (enum.hasMoreElements()) {
TreePoint tp = (TreePoint) (enum.nextElement());
examineTreePoint(tp, argv.use_jnidebug.booleanValue());
}
}
}
/**
* We want to show progress and write out temporary files every n minutes. This is called from
* 'examineFile'. It increases the file count, and, if the checkpoint is there, writes out.
*/
private void doCheckPointProcessing() {
if (System.currentTimeMillis()>checkpoint_next) {
checkpoint_next=checkpoint_next+CHECKPOINT_DELTA;
DecimalFormat df = new DecimalFormat(".00");
System.out.println("################## CHECKPOINT #######################");
System.out.println("### Files handled so far: " + filesHandled);
System.out.println("### that is " + df.format((double)filesHandled/(double)(System.currentTimeMillis()-checkpoint_start)*(double)1000.0) + " files/s");
System.out.println("################## CHECKPOINT #######################");
printCurrentResults(outputFile);
}
}
/**
* Write out results. This is called every few minutes and at the very end.
* Pass the name of the Excel file to be written (may be null if one should
* not write to the excel file)
*/
private boolean printCurrentResults(String use_outfile) {
Logger logCat = Logger.getLogger(CLASS + ".printCurrentResults");
// the results always go to stdout:
printResult();
// and maybe also to the Excel file.
if (use_outfile != null) {
// the results also go to an excel file (it is overwritten if it exists
try {
excelizeResult(use_outfile);
} catch (Exception exe) {
logCat.error("While writing Excel file", exe);
return false;
}
}
return true;
}
}
private final static String PACKAGE = "com.mplify.win2k.diskmon";
private final static String CLASS = PACKAGE + ".FileHogCheck";
private static final int ERROR_STATUS = 1; // Status for exit() calls in case of error
private static final int SUCCESS_STATUS = 0; // Status for exit() calls in case of success
private static final String ARGV_JNIDEBUG = "JNIDEBUG"; // argv keyword
private static final String ARGV_OUTFILE = "OUTFILE"; // argv keyword
private static final String ARGV_PROPERTIES = "PROPERTIES"; // argv keyword
private static final String ARGV_OVERWRITE = "OVERWRITE"; // argv keyword
private static final String ARGV_DEBUG = "DEBUG"; // argv keyword
private static final String PROPERTIES_KEY_TREEPOINTSET = "TREEPOINTSET"; // properties keyword
private static final String PROPERTIES_KEY_JNIDEBUG = "JNIDEBUG"; // properties keyword
private JNILayer jniLayer = new JNILayer(); // the interface to the Win32 subsystem
private Hashtable result = new Hashtable(); // this hashtable will be filled with HashKey -> HashValue mappings
private double now = (double) (System.currentTimeMillis()); // this is the 'now' moment, precomputed
private final static String[] TRAILER =
{
"+-------------------------------------------------------------------+",
"|This hamster checks a filesystem and lists the disk space occupied |",
"|by users on Win32 machines in an Microsoft Excel format, generated |",
"|with Jakarta POI. |",
"|Info is obtained by Win32 API calls through Java Native Interface. |",
"|(c) m-plify S.A. 2003; 47, av de la Liberté, L-1931 Luxembourg |",
"|Tél: +352 261846-1, Fax: +352 261846-46, Mail: support@m-plify.com |",
"+-------------------------------------------------------------------+" };
/**
* ------------------------------------------------------------------------------
* Locally used Exception, static so that it is not linked to the enclosing class
* ------------------------------------------------------------------------------
*/
public static class ExtremePrejudiceException extends Exception {
public ExtremePrejudiceException() {
super();
}
public ExtremePrejudiceException(String s) {
super(s);
}
}
/**
* ------------------------------------------------------------------------------
* This is just a container for data extracted during 'processArgv()', and
* an instance of the same is used there.
* ------------------------------------------------------------------------------
*/
private static class ArgvResult {
public Boolean use_jnidebug; // debug the JNI interface, set in static processArgv(), after which it is not null
public Boolean use_debug; // debug, set in static processArgv(), after which it is not null
public Boolean use_overwrite; // overwrite any existing Excel file if present
public String use_outfile; // name of the Excel output file, or (null)
public Vector use_treePoints; // file system tree points to use
}
/**
* ------------------------------------------------------------------------------
* This is just a container for a treepoint, i.e. a tuple
* ("name of directory or logical drive","name of machine holding security info or null")
* ------------------------------------------------------------------------------
*/
private static class TreePoint {
public String directory; // directory where to start the verification
public String machine; // machine to interrogate for sec.info. (null) means local machine
public TreePoint(String directory, String machine) {
this.directory = directory;
this.machine = machine;
}
}
/**
* ------------------------------------------------------------------------------
* This is just a container for a key in the result hashtable, the key
* contains the username + domain
* ------------------------------------------------------------------------------
*/
private static class HashKey {
private String user;
private String machine;
public HashKey(String user, String machine) {
if (user == null) {
throw new IllegalArgumentException("The passed 'user' is (null)");
}
if (machine == null) {
throw new IllegalArgumentException("The passed 'machine' is (null)");
}
this.user = user;
this.machine = machine;
}
public String getUser() {
return user;
}
public String getMachine() {
return machine;
}
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof HashKey))
return false;
HashKey other = (HashKey) obj;
return (user.equals(other.user) && machine.equals(other.machine));
}
public int hashCode() {
return user.hashCode() ^ machine.hashCode();
}
}
/**
* ------------------------------------------------------------------------------
* This is just a container for a value in the result hashtable, the value
* contains the number of files and the total size
* ------------------------------------------------------------------------------
*/
private static class HashValue {
private int fileCount;
private int fileCount_compressed;
private int fileCount_encrypted;
private int fileCount_tmp;
private int fileCount_os;
private int fileCount_hidden;
private long totalSize;
private double weightedDaysSum;
public HashValue(int fileCount, long fileSize) {
this.fileCount = fileCount;
this.totalSize = fileSize;
this.weightedDaysSum = 0;
this.fileCount_compressed = 0;
this.fileCount_encrypted = 0;
this.fileCount_tmp = 0;
this.fileCount_os = 0;
this.fileCount_hidden = 0;
}
public void addToWeightedDaysSum(double x) {
weightedDaysSum += x;
}
public void addToTotalSize(long x) {
totalSize += x;
}
public void addToFileCount(int x) {
fileCount += x;
}
public int getFileCount() {
return fileCount;
}
public int getCompressedFileCount() {
return fileCount_compressed;
}
public int getEncryptedFileCount() {
return fileCount_encrypted;
}
public int getHiddenFileCount() {
return fileCount_hidden;
}
public int getOSFileCount() {
return fileCount_os;
}
public int getTmpFileCount() {
return fileCount_tmp;
}
public long getTotalSize() {
return totalSize;
}
public double getWeightedDaysSum() {
return weightedDaysSum;
}
public void addToCompressedFileCount(int x) {
fileCount_compressed += x;
}
public void addToEncryptedFileCount(int x) {
fileCount_encrypted += x;
}
public void addToHiddenFileCount(int x) {
fileCount_hidden += x;
}
public void addToOSFileCount(int x) {
fileCount_os += x;
}
public void addToTmpFileCount(int x) {
fileCount_tmp += x;
}
}
/**
* Recursively examine a treepoint that we (ultimately) got from the properties file
*/
private void examineTreePoint(TreePoint tp, boolean debug) {
Logger logCat = LOGGER_examineTreePoint;
logCat.info("Examining treepoint ('" + tp.directory + "','" + tp.machine + "')");
//
// Now it's getting windows-specific: The treepoint name may indicate a file or
// directory on the local disk, on a remote disk, on a logical volume; it may
// also be a machine name in UNC (e.g. \\Blueshoes for a machine or \\Blueshoes\smbdato
// for a share or M: for a disk or M:\whatever for a file). We try to distinguish the
// case where the treepoint is just a UNC machine name (\\Blueshoes) as that case
// is special in the sense that we have to list the disk resources reachable for that
// machine before we are able to traverse any filesystem
//
if (looksLikeHostInUNC(tp.directory)) {
logCat.info("The following string has been recognized as a hostname in UNC: \"" + tp.directory + "\"");
//
// do a network enumeration on that presumed UNC hostname; we do not need to do recursive
// examination on the host, just list the drives directly underneath that 'node'
//
JNILayer layer = new JNILayer();
final int TYPE = Win32NetResource.TYPE_DISK; // we want disks (as opposed to print resources)
final int USAGE = Win32NetResource.USAGE_ALL; // any usage is acceptable
//
// make sure the string contains those silly backward slashes only (whoever thought
// that using an escape character as a tree node name separator would ever become
// accepted practice?)
//
String uncHost = tp.directory.replace('/', '\\');
JNILayer$Win32GetNetResources queryResult = layer.getWin32GetNetResources(TYPE, USAGE, uncHost, LOGGER_JNI.isDebugEnabled());
//
// the 'queryResult' should now contain a full list of the disks available on that machine
//
if (queryResult.getReturnValue()) {
// success, the queryResult contains a list of NetResources that can be examined
for (int j = 0; j < queryResult.getResultSize(); j++) {
Win32NetResource currentResource = queryResult.getResult(j);
if (logCat.isDebugEnabled()) {
// do some in-line prettyprinting
StringTokenizer tk = new StringTokenizer(currentResource.toString(), "\n");
while (tk.hasMoreTokens()) {
logCat.debug(tk.nextToken());
}
}
if (!currentResource.looksLikeInnerNode()) {
logCat.debug("The following network directory will be examined: \"" + currentResource.getRemoteName() + "\"");
File current = new File(currentResource.getRemoteName());
if (!current.exists()) {
logCat.error("'" + current + "' does not exist");
} else if (!current.canRead()) {
logCat.error("Can't read '" + current + "'");
return;
} else {
examineRecursively(current, tp.machine, 0);
}
} else {
logCat.error("There seems to be an inner node in the result set, named \"" + currentResource.getRemoteName() + "\"; skipped it!!");
}
}
} else {
// failure, let's see what happened
logCat.error("-- Network Query Failure for machine \"" + tp.directory + "\" --");
if (queryResult.getMyError() != 0) {
// myError is never null and does not need trimming
logCat.error("-- MyError==" + queryResult.getMyError() + ", " + queryResult.getMyErrorString());
}
if (queryResult.getLastError() != 0) {
String msg = queryResult.getLastErrorString();
if (msg != null) {
msg = msg.trim();
}
logCat.error("-- LastError==" + queryResult.getLastError() + ", " + msg);
}
if (queryResult.getLastNetError() != 0) {
String msg = queryResult.getLastNetErrorString();
if (msg != null) {
msg = msg.trim();
}
logCat.error("-- LastNetError==" + queryResult.getLastNetError() + ", " + msg);
}
}
} else {
logCat.info("The following string rather looks like a file or directory: \"" + tp.directory + "\"");
//
// make sure the string contains those silly backward slashes only (whoever thought
//
String uncHost = tp.directory.replace('/', '\\');
File current = new File(tp.directory);
if (!current.exists()) {
logCat.error("'" + tp.directory + "' does not exist");
} else if (!current.canRead()) {
logCat.error("Can't read '" + tp.directory + "'");
return;
} else {
examineRecursively(current, tp.machine, 0);
}
}
}
/**
* Print the 'result' hash to the Excel97 file with the given name
*/
private void excelizeResult(String outfile) throws Exception {
final String[] COLUMN_NAMES = { "User%Domain", "File count", "Total size", "Weighted age (days)", "Compressed files", "Hidden files", "OS files", "Encrypted files", "Temporary files" };
//
// first the workbook and a new sheet
//
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet(new SimpleDateFormat("yyyy.MM.dd").format(new java.util.Date()));
//
// need an array takes up the max number of characters over any cell in a row, so that we
// may adjust the row widths in the end
//
int[] maxCharsInCol = new int[COLUMN_NAMES.length];
//
// need two cell styles to format numerics, and one for the header
//
HSSFCellStyle styleWithDots = wb.createCellStyle();
styleWithDots.setDataFormat(HSSFDataFormat.getFormat("#,##0")); // retrieve a builtin format
HSSFCellStyle styleWithDecimals = wb.createCellStyle();
styleWithDecimals.setDataFormat(HSSFDataFormat.getFormat("0.00")); // retrieve a builtin format
HSSFCellStyle styleForHeader = wb.createCellStyle();
// setting the colour etc: could not get it to work properly
// styleForHeader.setFillBackgroundColor(HSSFColorConstants.BLUE);
// styleForHeader.setFillPattern((short)1); // someone forgot the FILL constant
//
// create the header row
//
{
final short rowIndex = 0;
HSSFRow row = sheet.createRow(rowIndex);
for (short colIndex = 0; colIndex < COLUMN_NAMES.length; colIndex++) {
HSSFCell c = row.createCell(colIndex);
c.setCellValue(COLUMN_NAMES[colIndex]);
c.setCellStyle(styleForHeader);
maxCharsInCol[colIndex] = COLUMN_NAMES[colIndex].length();
}
}
//
// loop over result, creating rows of data, starting at row 1
//
{
Enumeration enum = result.keys();
short rowIndex = 1;
while (enum.hasMoreElements()) {
HashKey hk = (HashKey) (enum.nextElement());
HashValue hv = (HashValue) (result.get(hk));
HSSFRow row = sheet.createRow(rowIndex++);
{
final short colIndex = 0;
// row 0 contains "user%domain"; the cell will go to string type on setCellValue()
HSSFCell c = row.createCell(colIndex);
String value = hk.getUser() + "%" + hk.getMachine();
c.setCellValue(value);
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], value.length());
}
{
final short colIndex = 1;
// contains file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getFileCount()).length());
}
{
final short colIndex = 2;
// contains total file size; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
double value = hv.getTotalSize();
c.setCellValue(value);
// set up a format that shows some formatting (maybe this should be a common cell style?)
c.setCellStyle(styleWithDots);
// same thing in Java to determine the length of the result
{
DecimalFormat df = new DecimalFormat("#,##0");
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], df.format(value).length());
}
}
{
final short colIndex = 3;
// contains weighted average in days; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
double value = 0;
if (hv.getTotalSize() != 0) {
value = hv.getWeightedDaysSum() / ((double) hv.getTotalSize());
}
c.setCellValue(value);
// set up a format that shows some formatting (maybe this should be a common cell style?)
c.setCellStyle(styleWithDecimals);
// same thing in Java to determine the length of the result
{
DecimalFormat df = new DecimalFormat("0.00");
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], df.format(value).length());
}
}
{
final short colIndex = 4;
// contains compressed file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getCompressedFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getCompressedFileCount()).length());
}
{
final short colIndex = 5;
// contains hidden file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getHiddenFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getHiddenFileCount()).length());
}
{
final short colIndex = 6;
// contains operating system file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getOSFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getOSFileCount()).length());
}
{
final short colIndex = 7;
// contains encrypted file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getEncryptedFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getEncryptedFileCount()).length());
}
{
final short colIndex = 8;
// contains temporary file count; the cell will go to numeric type on setCellValue()
HSSFCell c = row.createCell(colIndex);
c.setCellValue(hv.getTmpFileCount());
maxCharsInCol[colIndex] = Math.max(maxCharsInCol[colIndex], Integer.toString(hv.getTmpFileCount()).length());
}
}
}
//
// loop over columns, to set an agreeable width, using the maxes collected
// the width is given in units of 1/256th of a character width
//
for (short colIndex = 0; colIndex < COLUMN_NAMES.length; colIndex++) {
final int ADD_CHARS = 5;
sheet.setColumnWidth(colIndex, (short) ((maxCharsInCol[colIndex] + ADD_CHARS) * 256));
}
OutputStream os = new BufferedOutputStream(new FileOutputStream(outfile));
wb.write(os);
os.close();
}
/**
* Generate a key for the properties file examination
*/
private static String generate_PROPERTIES_KEY_TREEPOINT_DIRECTORY(int x) {
return "TREEPOINT." + x + ".DIRECTORY";
}
/**
* Generate a key for the properties file examination
*/
private static String generate_PROPERTIES_KEY_TREEPOINT_MACHINE(int x) {
return "TREEPOINT." + x + ".MACHINE";
}
/**
* main()
*/
public static void main(String[] argv) {
Logger logCat = LOGGER_main;
//
// prepare logging, set the default level to INFO
//
Layout layout = new SimpleLayout();
Logger.getRoot().addAppender(new ConsoleAppender(layout, ConsoleAppender.SYSTEM_OUT));
Logger.getRoot().setLevel(Level.INFO);
//
// read from the commandline
//
ArgvResult argvResult = null;
try {
if (argv == null) {
argv = new String[0];
}
if (argv.length == 1 && ("HELP".equalsIgnoreCase(argv[0]) || "-h".equalsIgnoreCase(argv[0]) || "--help".equalsIgnoreCase(argv[0]) || "/h".equalsIgnoreCase(argv[0]))) {
printUsage();
System.exit(SUCCESS_STATUS);
} else {
argvResult = processArgv(argv);
}
} catch (Exception exe) {
logCat.error("While handling command-line arguments", exe);
System.exit(ERROR_STATUS);
}
//
// maybe set the log4j level to DEBUG, depending on what was given on the command line
// The 'JNI' logger determines how the JNI code will act
//
if (argvResult.use_debug.booleanValue()) {
Logger.getRoot().setLevel(Level.DEBUG);
}
if (argvResult.use_jnidebug.booleanValue()) {
Logger.getLogger("JNI").setLevel(Level.DEBUG);
} else {
Logger.getLogger("JNI").setLevel(Level.INFO);
}
//
// get the 'FileHogCheck', using values possibly set through the commandline
// Note that we use the same file for temporary results as for the final result
//
FileHogCheck fhc = new FileHogCheck(argvResult,argvResult.use_outfile);
//
// on return the 'fhc' contains data, which we now output
//
boolean res = fhc.printCurrentResults(argvResult.use_outfile);
if (res) {
System.exit(SUCCESS_STATUS);
}
else {
System.exit(ERROR_STATUS);
}
}
/**
* Parse the passed 'value' into a boolean - many values are acceptable but if none of these
* fits, an exception is thrown.
*/
private static boolean parseBoolean(String value) {
String lvalue = value.toLowerCase(); // trimming already done
if (lvalue.equals("yes") || lvalue.equals("true") || lvalue.equals("on") || lvalue.equals("1") || lvalue.equals("y") || lvalue.equals("t")) {
return true;
}
if (lvalue.equals("no") || lvalue.equals("false") || lvalue.equals("off") || lvalue.equals("0") || lvalue.equals("n") || lvalue.equals("f")) {
return false;
}
throw new IllegalArgumentException("The passed value is not a boolean");
}
/**
* Parse the line containing the list of comma-separated atoms
* This list has been obtained from a 'properties' file and its String value
* is in the passed 'matchThis'. The key name is also passed as 'keyName'
* in order to print good debugging information.
*/
private static HashSet parseCommaSeparatedAtoms(String matchThis, String keyName) {
HashSet set = new HashSet();
//
// Parse using ORO
//
Perl5Compiler compiler = new Perl5Compiler();
Perl5Matcher matcher = new Perl5Matcher();
Pattern patternFront = null;
Pattern patternBack = null;
try {
patternFront = compiler.compile("^([\\w\\-]+)(.*)"); // we are looking for things that start like this
patternBack = compiler.compile("^\\s*,\\s*([\\w\\-]+)(.*)"); // and continue like this
} catch (MalformedPatternException exe) {
}
if (!matcher.contains(matchThis, patternFront)) {
throw new IllegalStateException("The value for the key \"" + keyName + "\" could not be parsed, it is not a comma-separated list of [A-Za-z0-9_-]");
} else {
// matcher has already the first result in its guts
MatchResult result = matcher.getMatch();
// group '0' is the entire match, we are interested in the first value, i.e. group 1
set.add(result.group(1));
matchThis = result.group(2);
while (matcher.contains(matchThis, patternBack)) {
result = matcher.getMatch();
set.add(result.group(1));
matchThis = result.group(2);
}
// verify whether that is all
if (matchThis.trim().length() != 0) {
throw new IllegalStateException("The value for the key \"" + keyName + "\" has some unparseable stuff at the end: \"" + matchThis + "\"");
}
}
return set;
}
/**
* Print the 'result' hash in a primitive fashion
*/
public void printResult() {
Enumeration enum = result.keys();
while (enum.hasMoreElements()) {
HashKey hk = (HashKey) (enum.nextElement());
System.out.println("User '" + hk.getUser() + "' on machine '" + hk.getMachine() + "'");
HashValue hv = (HashValue) (result.get(hk));
{
double avg = 0;
if (hv.getTotalSize() != 0) {
avg = hv.getWeightedDaysSum() / ((double) hv.getTotalSize());
}
DecimalFormat df = new DecimalFormat(".00");
System.out.println(" Total files...........: " + hv.getFileCount() + ", size=" + hv.getTotalSize() + " byte, avg=" + df.format(avg) + " days");
}
if (hv.getCompressedFileCount() > 0) {
System.out.println(" Compressed files......: " + hv.getCompressedFileCount());
}
if (hv.getHiddenFileCount() > 0) {
System.out.println(" Hidden files..........: " + hv.getHiddenFileCount());
}
if (hv.getOSFileCount() > 0) {
System.out.println(" Operating system files: " + hv.getOSFileCount());
}
if (hv.getEncryptedFileCount() > 0) {
System.out.println(" Encrypted files.......: " + hv.getEncryptedFileCount());
}
if (hv.getTmpFileCount() > 0) {
System.out.println(" Temporary files.......: " + hv.getTmpFileCount());
}
}
}
/**
* Print usage definition to stderr
*/
private static void printUsage() {
for (int i = 0; i < TRAILER.length; i++) {
System.err.println(TRAILER[i]);
}
System.err.println("USAGE: Call " + CLASS + " with the following keywords and arguments:");
System.err.println(" " + ARGV_JNIDEBUG.toUpperCase());
System.err.println(" if present, the C part of the program prints out debug info");
System.err.println(" " + ARGV_PROPERTIES.toUpperCase() + " <value>");
System.err.println(" where <value> is the name of a file holding properties");
System.err.println(" " + ARGV_OUTFILE.toUpperCase() + " <value>");
System.err.println(" where <value> is the name of an Excel97 file that will be written");
System.err.println(" (result goes to stdout if that argument is not present)");
System.err.println(" " + ARGV_OVERWRITE.toUpperCase());
System.err.println(" if present, the Excel97 will be overwritten if it exists");
System.err.println(" " + ARGV_DEBUG.toUpperCase());
System.err.println(" if present, the log level will do to debug");
}
/**
* Process the argv passed to main(). Throws an exception if there is a problem (some exceptions
* are caught). On problem, a message is written to 'stderr'. Some stuff is logged using log4j
*/
private static ArgvResult processArgv(String[] argv) throws Exception {
Logger logCat = LOGGER_processArgv;
String local_properties = null; // no properties have been registered
String local_outfile = null; // no output file has been registered
Boolean processed_jnidebug = Boolean.FALSE; // switch-on flag is off
Boolean processed_overwrite = Boolean.FALSE; // switch-on flag is off
Boolean processed_debug = Boolean.FALSE; // switch-on flag is off
ArgvResult result = new ArgvResult();
//
// process command line
//
{
String expect = null; // what value to expect next
for (int i = 0; i < argv.length; i++) {
if (expect == null) {
// state: expecting a new keyword
// get a new keyword (which may be the start of a new keyword-value pair)
if (argv[i].equalsIgnoreCase(ARGV_JNIDEBUG)) {
processed_jnidebug = Boolean.TRUE;
expect = null;
} else if (argv[i].equalsIgnoreCase(ARGV_DEBUG)) {
processed_debug = Boolean.TRUE;
expect = null;
} else if (argv[i].equalsIgnoreCase(ARGV_OVERWRITE)) {
processed_overwrite = Boolean.TRUE;
expect = null;
} else if (argv[i].equalsIgnoreCase(ARGV_PROPERTIES)) {
expect = ARGV_PROPERTIES;
} else if (argv[i].equalsIgnoreCase(ARGV_OUTFILE)) {
expect = ARGV_OUTFILE;
} else {
printUsage();
System.err.println("******************");
System.err.println("ERROR: Unknown keyword \"" + argv[i] + "\" encountered");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
} else if (expect.equalsIgnoreCase(ARGV_PROPERTIES)) {
expect = null;
local_properties = argv[i];
} else if (expect.equalsIgnoreCase(ARGV_OUTFILE)) {
expect = null;
local_outfile = argv[i];
} else {
throw new IllegalStateException("Unhandled case of 'expect': \"" + expect + "\"");
}
}
}
//
// Postprocess
//
FileInputStream propFis = null;
if (local_properties != null) {
try {
propFis = new FileInputStream(new File(local_properties));
} catch (Exception exe) {
System.err.println("******************");
System.err.println("ERROR: Value for " + ARGV_PROPERTIES + " (" + local_properties + ") is an invalid or an inaccessible file");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
}
//
// Assign defaults
//
result.use_jnidebug = processed_jnidebug; // immediate
result.use_debug = processed_debug; // immediate
result.use_treePoints = new Vector(); // will be filled in, empty by default
result.use_outfile = local_outfile; // immediate
result.use_overwrite = processed_overwrite; // immediate
//
// Check whether the output file can be written and can be *over*written
//
if (result.use_outfile != null) {
File outfile = new File(result.use_outfile);
if (outfile.exists() && !result.use_overwrite.booleanValue()) {
System.err.println("******************");
System.err.println("Output file '" + outfile + "' exists and cannot be overwritten");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
if (outfile.exists() && !outfile.isFile()) {
System.err.println("******************");
System.err.println("Output file '" + outfile + "' is not a file; cannot overwrite");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
if (!outfile.exists()) {
// try to create the file
if (!outfile.createNewFile()) {
System.err.println("******************");
System.err.println("Output file '" + outfile + "' could not be created");
System.err.println("******************");
throw new ExtremePrejudiceException();
}
}
}
//
// Read properties, if any
//
if (propFis != null) {
Properties props = new Properties();
props.load(propFis);
propFis.close();
//
// handle JNIDEBUG, i.e. set the jnidebug to true if the properties-value is true, leave it otherwise
//
if (props.containsKey(PROPERTIES_KEY_JNIDEBUG)) {
result.use_jnidebug = new Boolean(result.use_jnidebug.booleanValue() || parseBoolean(props.getProperty(PROPERTIES_KEY_JNIDEBUG)));
}
//
// handle the set of treepoints
//
{
Set treepointSet = new HashSet();
if (props.containsKey(PROPERTIES_KEY_TREEPOINTSET)) {
HashSet treePointSet = parseCommaSeparatedAtoms(props.getProperty(PROPERTIES_KEY_TREEPOINTSET), PROPERTIES_KEY_TREEPOINTSET);
Iterator iter = treePointSet.iterator();
while (iter.hasNext()) {
String id = (String) (iter.next());
int num;
try {
num = Integer.parseInt(id);
} catch (NumberFormatException exe) {
System.err.println("******************");
System.err.println("ERROR: Non-numeric id '" + id + "' found in treepoint-set list in properties file...skipped entry");
System.err.println("******************");
continue;
}
String directoryKey = generate_PROPERTIES_KEY_TREEPOINT_DIRECTORY(num);
String machineKey = generate_PROPERTIES_KEY_TREEPOINT_MACHINE(num);
if (!props.containsKey(machineKey)) {
// skip it
System.err.println("******************");
System.err.println("ERROR: No value for key '" + machineKey + "' found; check your properties file...skipped entry");
System.err.println("******************");
} else if (!props.containsKey(directoryKey)) {
// skip it
System.err.println("******************");
System.err.println("ERROR: No value for key '" + directoryKey + "' found; check your properties file...skipped entry");
System.err.println("******************");
} else {
String directory = props.getProperty(directoryKey);
String machine = props.getProperty(machineKey);
if (machine.equalsIgnoreCase("null") || machine.equalsIgnoreCase("(null)")) {
machine = null;
}
result.use_treePoints.add(new TreePoint(directory, machine));
logCat.info("Loaded new treepoint ('" + directory + "','" + machine + "')");
}
}
}
}
} else {
System.err.println("******************");
System.err.println("ERROR: You must specify a properties file with " + ARGV_PROPERTIES);
System.err.println("******************");
throw new ExtremePrejudiceException();
}
return result;
}
private final static Logger LOGGER_examineFile = Logger.getLogger(CLASS + ".examineFile"); // a logger
private final static Logger LOGGER_examineRecursively = Logger.getLogger(CLASS + ".examineRecursively"); // a logger
private final static Logger LOGGER_examineTreePoint = Logger.getLogger(CLASS + ".examineTreePoint"); // a logger
private final static Logger LOGGER_JNI = Logger.getLogger("JNI"); // logger for JNI
private final static Logger LOGGER_main = Logger.getLogger(CLASS + ".main"); // a logger
private final static Logger LOGGER_processArgv = Logger.getLogger(CLASS + ".processArgv"); // a logger
/**
* Examine something classified as a 'file'; the Win32 filename (in UNC format or not) is given
* and should (hopefully) be resolved by the JVM into something valid.
*/
private void examineFile(File current, String machine) {
filesHandled++;
if (checkpoint_countdown<=0) {
doCheckPointProcessing();
checkpoint_countdown=CHECKPOINT_COUNTDOWN;
}
else {
checkpoint_countdown--;
}
JNILayer$Win32GetFileInformation fileInfo = jniLayer.getWin32GetFileInformation(current.getAbsolutePath(), machine, LOGGER_JNI.isDebugEnabled());
if (fileInfo.getReturnValue()) {
// all went well, so add the result to the totals
HashKey hk = new HashKey(fileInfo.getAccountName(), fileInfo.getDomainName());
HashValue hv = null;
if (result.containsKey(hk)) {
hv = (HashValue) (result.get(hk));
hv.addToFileCount(1);
hv.addToTotalSize(fileInfo.getFileSize());
} else {
hv = new HashValue(1, fileInfo.getFileSize());
result.put(hk, hv);
}
// also add to the 'weighted creation time', which is the numerator of a weighted
// sum of (now-creationDate); weighs are the file sizes...
if (fileInfo.isFileCreationTimeOk()) {
long creation = fileInfo.getFileCreationTime().getTime();
double delta_days = (now - (double) creation) / (3600 * 24 * 1000);
hv.addToWeightedDaysSum(delta_days * fileInfo.getFileSize());
}
// also flag any special stuff
{
BitSet flags = fileInfo.getFileAttributeFlags();
if (flags.get(fileInfo.FILE_ATTRIBUTE_COMPRESSED)) {
hv.addToCompressedFileCount(1);
}
if (flags.get(fileInfo.FILE_ATTRIBUTE_ENCRYPTED)) {
hv.addToEncryptedFileCount(1);
}
if (flags.get(fileInfo.FILE_ATTRIBUTE_HIDDEN)) {
hv.addToHiddenFileCount(1);
}
if (flags.get(fileInfo.FILE_ATTRIBUTE_SYSTEM)) {
hv.addToOSFileCount(1);
}
if (flags.get(fileInfo.FILE_ATTRIBUTE_TEMPORARY)) {
hv.addToTmpFileCount(1);
}
}
} else {
// some error occurred
String msg = "Some error occurred for '" + current.getAbsolutePath() + "' with Win32GetFileInformation";
switch (fileInfo.getErrorWith()) {
case fileInfo.ERROR_WITH_FILE_TIME_TO_SYSTEM_TIME :
msg += " in FileTimeToSystemTime(); last error = " + fileInfo.getLastError();
break;
case fileInfo.ERROR_WITH_GET_FILE_ATTRIBUTES_EX :
msg += " in GetFileAttributesEx(); last error = " + fileInfo.getLastError();
break;
case fileInfo.ERROR_WITH_GET_NAMED_SECURITY_INFO :
msg += " in GetNamedSecurityInfo(); error code = " + fileInfo.getLastError();
break;
case fileInfo.ERROR_WITH_LOOKUP_ACCOUNT_SID :
msg += " in ErrorWithLookupAccountSid(); last code = " + fileInfo.getLastError();
break;
case fileInfo.ERROR_WITH_PARAMETERS :
msg += " -- bad parameters"; // no error code or last error here
break;
default :
msg += " in unknown procedure";
}
String ext = fileInfo.getLastErrorString();
if (ext != null) {
msg += ": \"";
msg += ext.trim();
msg += "\"";
}
LOGGER_examineFile.warn(msg);
}
}
/**
* Do a depth-first recursive examination of a file 'current' (which might be a file or
* a directory), with security information taken from 'machine'. The current depth of the
* examination is 'depth'; the Win32 filename (in UNC format or not) is given
* and should (hopefully) be resolved by the JVM into something valid.
*/
private void examineRecursively(File current, String machine, int depth) {
if (LOGGER_examineRecursively.isDebugEnabled()) {
LOGGER_examineRecursively.debug("Checking '" + current.getAbsolutePath() + "'");
}
if (current.isFile()) {
examineFile(current, machine);
} else if (current.isDirectory()) {
File[] files = current.listFiles();
if (files == null) {
LOGGER_examineRecursively.error("List of files obtained for '" + current.getAbsolutePath() + "' is (null); current depth is " + depth);
} else {
for (int i = 0; i < files.length; i++) {
examineRecursively(files[i], machine, depth + 1);
}
}
} else {
LOGGER_examineRecursively.warn("File '" + current.getAbsolutePath() + "' is neither a file nor a directory; skipped");
}
}
/**
* Check whether a string looks like a hostname in UNC (Microsoft Universal Naming Convention)
*/
private static boolean looksLikeHostInUNC(String str) {
if (str == null) {
return false;
}
str = str.trim();
if (str.length() < 3) {
return false;
}
if (str.charAt(0) != '\\' && str.charAt(0) != '/' && str.charAt(1) != '\\' && str.charAt(1) != '/') {
return false;
}
StringTokenizer st = new StringTokenizer(str, "\\/");
if (st.countTokens() > 1) {
return false;
}
return true;
}
private long checkpoint_countdown; // we do not want to do checkpoint processing at every file; this is a countdown
private static final long CHECKPOINT_COUNTDOWN = 20; // when will checkpoints processing be done? every 20 files
private static final long CHECKPOINT_DELTA = 1*60*1000; // when will checkpoints be?
private long checkpoint_next; // a time value: when will the next checkpoint be
private long checkpoint_start; // a time value: when did processing start
private int filesHandled = 0; // number of files already handled (0 at the start)
private String outputFile; // name of the file we will write Excel results at every checkpoint
/**
* Constructor runs the algorithm. Final Output is left to the caller.
* However, we temporarily write out the result Excel file. To do so, the
* filename of the Excel file is passed. If it is (null), nothing will be written.
* Fo flexibility, the name of the ouptut file in ArgvResult is NOT used.
*/
public FileHogCheck(ArgvResult argv,String outputFile) {
Logger logCat = Logger.getLogger(CLASS + ".FileHogCheck");
//
// Initialize checkpoint time accounting
//
checkpoint_start = System.currentTimeMillis();
checkpoint_next = checkpoint_start + CHECKPOINT_DELTA;
checkpoint_countdown = CHECKPOINT_COUNTDOWN;
//
// set up the intermediate result file
//
this.outputFile = outputFile;
//
// loop over all the treepoints
//
{
Enumeration enum = argv.use_treePoints.elements();
while (enum.hasMoreElements()) {
TreePoint tp = (TreePoint) (enum.nextElement());
examineTreePoint(tp, argv.use_jnidebug.booleanValue());
}
}
}
/**
* We want to show progress and write out temporary files every n minutes. This is called from
* 'examineFile'. It increases the file count, and, if the checkpoint is there, writes out.
*/
private void doCheckPointProcessing() {
if (System.currentTimeMillis()>checkpoint_next) {
checkpoint_next=checkpoint_next+CHECKPOINT_DELTA;
DecimalFormat df = new DecimalFormat(".00");
System.out.println("################## CHECKPOINT #######################");
System.out.println("### Files handled so far: " + filesHandled);
System.out.println("### that is " + df.format((double)filesHandled/(double)(System.currentTimeMillis()-checkpoint_start)*(double)1000.0) + " files/s");
System.out.println("################## CHECKPOINT #######################");
printCurrentResults(outputFile);
}
}
/**
* Write out results. This is called every few minutes and at the very end.
* Pass the name of the Excel file to be written (may be null if one should
* not write to the excel file)
*/
private boolean printCurrentResults(String use_outfile) {
Logger logCat = Logger.getLogger(CLASS + ".printCurrentResults");
// the results always go to stdout:
printResult();
// and maybe also to the Excel file.
if (use_outfile != null) {
// the results also go to an excel file (it is overwritten if it exists
try {
excelizeResult(use_outfile);
} catch (Exception exe) {
logCat.error("While writing Excel file", exe);
return false;
}
}
return true;
}
}