From 77f5431e3b77c3d16693de34c21189d3f960ec41 Mon Sep 17 00:00:00 2001 From: Jiri Vanek Date: Mon, 2 Dec 2013 16:04:32 +0100 Subject: Better validation of crytical dirs with proper message on startup --- .../jnlp/config/DeploymentConfiguration.java | 28 +- .../jnlp/config/DirectoryValidator.java | 379 +++++++++++++++++++++ .../sourceforge/jnlp/resources/Messages.properties | 3 + netx/net/sourceforge/jnlp/runtime/Boot.java | 9 +- 4 files changed, 405 insertions(+), 14 deletions(-) create mode 100644 netx/net/sourceforge/jnlp/config/DirectoryValidator.java (limited to 'netx') diff --git a/netx/net/sourceforge/jnlp/config/DeploymentConfiguration.java b/netx/net/sourceforge/jnlp/config/DeploymentConfiguration.java index 02a4933..a132864 100644 --- a/netx/net/sourceforge/jnlp/config/DeploymentConfiguration.java +++ b/netx/net/sourceforge/jnlp/config/DeploymentConfiguration.java @@ -36,6 +36,7 @@ import java.util.Properties; import java.util.Set; import javax.naming.ConfigurationException; +import javax.swing.JOptionPane; import net.sourceforge.jnlp.cache.CacheLRUWrapper; import net.sourceforge.jnlp.runtime.JNLPRuntime; @@ -213,9 +214,9 @@ public final class DeploymentConfiguration { /** is it mandatory to load the system properties? */ private boolean systemPropertiesMandatory = false; - /** The system's deployment.config file */ + /** The system's subdirResult deployment.config file */ private File systemPropertiesFile = null; - /** The user's deployment.config file */ + /** The user's subdirResult deployment.config file */ private File userPropertiesFile = null; /*default user file*/ @@ -280,7 +281,7 @@ public final class DeploymentConfiguration { Map> systemProperties = null; /* - * First, try to read the system's deployment.config file to find if + * First, try to read the system's subdirResult deployment.config file to find if * there is a system-level deployment.poperties file */ @@ -304,7 +305,7 @@ public final class DeploymentConfiguration { } /* - * Third, read the user's deployment.properties file + * Third, read the user's subdirResult deployment.properties file */ userPropertiesFile = userFile; Map> userProperties = loadProperties(ConfigType.User, userPropertiesFile, false); @@ -713,6 +714,8 @@ public final class DeploymentConfiguration { int errors = 0; String PRE_15_DEPLOYMENT_DIR = ".icedtea"; String LEGACY_USER_HOME = System.getProperty("user.home") + File.separator + PRE_15_DEPLOYMENT_DIR; + File configDir = new File(Defaults.USER_CONFIG_HOME); + File cacheDir = new File(Defaults.USER_CACHE_HOME); File legacyUserDir = new File(LEGACY_USER_HOME); if (legacyUserDir.exists()) { OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Legacy configuration and cache found. Those will be now transported to new locations"); @@ -723,11 +726,9 @@ public final class DeploymentConfiguration { OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, "Preparing new directories:"); OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " " + Defaults.USER_CONFIG_HOME); - File f1 = new File(Defaults.USER_CONFIG_HOME); - errors += resultToStd(f1.mkdirs()); + errors += resultToStd(configDir.mkdirs()); OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, " " + Defaults.USER_CACHE_HOME); - File f2 = new File(Defaults.USER_CACHE_HOME); - errors += resultToStd(f2.mkdirs()); + errors += resultToStd(cacheDir.mkdirs()); String legacySecurity = LEGACY_USER_HOME + File.separator + "security"; String currentSecurity = Defaults.USER_SECURITY; @@ -786,16 +787,23 @@ public final class DeploymentConfiguration { } else { OutputController.getLogger().log("System is already following XDG .cache and .config specifications"); try { - OutputController.getLogger().log("config: " + Defaults.USER_CONFIG_HOME + " file exists: " + new File(Defaults.USER_CONFIG_HOME).exists()); + OutputController.getLogger().log("config: " + Defaults.USER_CONFIG_HOME + " file exists: " + configDir.exists()); } catch (Exception ex) { OutputController.getLogger().log(ex); } try { - OutputController.getLogger().log("cache: " + Defaults.USER_CACHE_HOME + " file exists:" + new File(Defaults.USER_CACHE_HOME)); + OutputController.getLogger().log("cache: " + Defaults.USER_CACHE_HOME + " file exists:" + cacheDir.exists()); } catch (Exception ex) { OutputController.getLogger().log(ex); } } + //this call should endure even if (ever) will migration code be removed + DirectoryValidator.DirectoryCheckResults r = new DirectoryValidator().ensureDirs(); + if (!JNLPRuntime.isHeadless()) { + if (r.getFailures() > 0) { + JOptionPane.showMessageDialog(null, r.getMessage()); + } + } } diff --git a/netx/net/sourceforge/jnlp/config/DirectoryValidator.java b/netx/net/sourceforge/jnlp/config/DirectoryValidator.java new file mode 100644 index 0000000..3d526fd --- /dev/null +++ b/netx/net/sourceforge/jnlp/config/DirectoryValidator.java @@ -0,0 +1,379 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package net.sourceforge.jnlp.config; + +import static net.sourceforge.jnlp.runtime.Translator.R; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.util.FileUtils; +import net.sourceforge.jnlp.util.logging.OutputController; + +/** + * + * @author jvanek + */ +public class DirectoryValidator { + + /** + * This class is holding results of directory validation. + * Various errors like can not read, write create dir can apeear + * For sumaries of results are here getPasses, getFailures methods + * + * Individual results can be read from results field, or converted to string + */ + public static class DirectoryCheckResults { + public final List results; + + /** + * Wraps results so we can make some statistics or convert to message + * @param results + */ + public DirectoryCheckResults(List results) { + this.results = results; + } + + /** + * + * @return sum of passed checks, 0-3 per result + */ + public int getPasses() { + int passes = 0; + for (DirectoryCheckResult directoryCheckResult : results) { + passes += directoryCheckResult.getPasses(); + } + return passes; + } + + /** + * + * @return sum of failed checks, 0-3 per results + */ + public int getFailures() { + int failures = 0; + for (DirectoryCheckResult directoryCheckResult : results) { + failures += directoryCheckResult.getFailures(); + } + return failures; + } + + /** + * The result have one reuslt per line, separated by \n + * as is inherited from result.getMessage() method. + * + * @return all results connected. + */ + public String getMessage(){ + return resultsToString(results); + } + + /** + * using getMessage + * @return + */ + @Override + public String toString() { + return getMessage(); + } + + + + public static String resultsToString(List results) { + StringBuilder sb = new StringBuilder(); + for (DirectoryCheckResult r : results) { + if (r.getFailures() >0 ){ + sb.append(r.getMessage()); + } + } + return sb.toString(); + } + + } + + /** + * Is storing result of directory validation. + * + * validated are existence of directory + * whether it is directory + * if it have read/write permissions + */ + public static class DirectoryCheckResult { + + //do exist? + public boolean exists = true; + //is dir? + public boolean isDir = true; + //can be read, written to? + public boolean correctPermissions = true; + //have correct subdir? - this implies soe rules, when subdirecotry of some + //particular directory have weeker permissions + public DirectoryCheckResult subDir = null; + //actual tested directory + private final File testedDir; + + public DirectoryCheckResult(File testedDir) { + this.testedDir = testedDir; + } + + public static String notExistsMessage(File f) { + return R("DCmaindircheckNotexists", f.getAbsolutePath()); + } + + public static String notDirMessage(File f) { + return R("DCmaindircheckNotdir", f.getAbsolutePath()); + } + + public static String wrongPermissionsMessage(File f) { + return R("DCmaindircheckRwproblem", f.getAbsolutePath()); + } + + private static int booleanToInt(boolean b) { + if (b) { + return 1; + } else { + return 0; + } + } + + + /** + * count passes of this result (0-3, both inclusive). + */ + public int getPasses() { + int subdirs = 0; + if (subDir != null) { + subdirs = subDir.getPasses(); + } + return booleanToInt(exists) + + booleanToInt(isDir) + + booleanToInt(correctPermissions) + + subdirs; + } + + /* + * count failures of this result (0-3, both inclusive). + */ + public int getFailures() { + int max = 3; + if (subDir != null) { + max = 2 * max; + } + return max - getPasses(); + } + + /** + * Convert results to string. + * Each failure by line. PAsses are not mentioned + * The subdirectory (and it subdirectories are included to ) + * + * @return string with \n, or/and ended by \n + */ + public String getMessage() { + StringBuilder sb = new StringBuilder(); + if (!exists) { + sb.append(notExistsMessage(testedDir)).append("\n"); + } + if (!isDir) { + sb.append(notDirMessage(testedDir)).append("\n"); + } + if (!correctPermissions) { + sb.append(wrongPermissionsMessage(testedDir)).append("\n"); + } + + if (subDir != null) { + String s = subDir.getMessage(); + if (!s.isEmpty()) { + sb.append(s); + } + } + return sb.toString(); + } + + @Override + public String toString() { + return getMessage(); + } + } + + + private final List dirsToCheck; + + + /** + * Creates DirectoryValidator to ensure given directories + * + * @param dirsToCheck + */ + public DirectoryValidator(List dirsToCheck) { + this.dirsToCheck = dirsToCheck; + } + + + /** + * Creates DirectoryValidator to ensure directories read from + * user (if any - default otherwise ) settings via keys: + *
  • KEY_USER_CACHE_DIR
  • + *
  • KEY_USER_PERSISTENCE_CACHE_DIR
  • + *
  • KEY_SYSTEM_CACHE_DIR
  • + *
  • KEY_USER_LOG_DIR
  • + *
  • KEY_USER_TMP_DIR
  • + *
  • KEY_USER_LOCKS_DIR
  • + */ + public DirectoryValidator() { + dirsToCheck = new ArrayList(6); + DeploymentConfiguration dc = JNLPRuntime.getConfiguration(); + String[] keys = new String[]{ + DeploymentConfiguration.KEY_USER_CACHE_DIR, + DeploymentConfiguration.KEY_USER_PERSISTENCE_CACHE_DIR, + DeploymentConfiguration.KEY_SYSTEM_CACHE_DIR, + DeploymentConfiguration.KEY_USER_LOG_DIR, + DeploymentConfiguration.KEY_USER_TMP_DIR, + DeploymentConfiguration.KEY_USER_LOCKS_DIR}; + for (String key : keys) { + String value = dc.getProperty(key); + if (value == null) { + OutputController.getLogger().log(OutputController.Level.MESSAGE_DEBUG, "WARNING: key " + key + " has no value, setting to default value"); + value = Defaults.getDefaults().get(key).getValue(); + } + if (value == null) { + if (JNLPRuntime.isDebug()) { + OutputController.getLogger().log(OutputController.Level.MESSAGE_DEBUG, "WARNING: key " + key + " has no value, skipping"); + } + continue; + } + File f = new File(value); + dirsToCheck.add(f); + } + } + + /** + * This method is ensuring, that specified directories will exists after + * call and will have enough permissions. + * + * This methods is trying to create the directories if they are not present + * and is testing if can be written inside. All checks are done in bulk. If + * one or more defect is found, user is warned via dialogue in gui mode + * (again in bulk). In headless mode stdout/stderr is enough, as application + * (both gui and headless) should not stop to work, but continue to run with + * hope that corrupted dirs will not be necessary + */ + public DirectoryCheckResults ensureDirs() { + return ensureDirs(dirsToCheck); + } + + static DirectoryCheckResults ensureDirs(List dirs) { + List result = new ArrayList(dirs.size()); + for (File f : dirs) { + if (f.exists()) { + DirectoryCheckResult r = testDir(f, true, true); + result.add(r); + continue; + } + if (!f.mkdirs()) { + OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "ERROR: Directory " + f.getAbsolutePath() + " does not exist and has not been created"); + } else { + OutputController.getLogger().log(OutputController.Level.MESSAGE_DEBUG,"OK: Directory " + f.getAbsolutePath() + " did not exist but has been created"); + } + DirectoryCheckResult r = testDir(f, true, true); + result.add(r); + } + return new DirectoryCheckResults(result); + } + + /** + * This method is package private for testing purposes only. + * + * This method verify that directory exists, is directory, file and + * directory can be created, file can be written into, and subdirectory can + * be written into. + * + * Some steps may looks like redundant, but some permission settings really + * alow to create file but not directory and vice versa. Also some settings + * can allow to create file or directory which can not be written into. (eg + * ACL or network disks) + */ + static DirectoryCheckResult testDir(File f, boolean verbose, boolean testSubdir) { + DirectoryCheckResult result = new DirectoryCheckResult(f); + if (!f.exists()) { + if (verbose) { + OutputController.getLogger().log(OutputController.Level.ERROR_ALL, DirectoryCheckResult.notExistsMessage(f)); + } + result.exists = false; + } + if (!f.isDirectory()) { + if (verbose) { + OutputController.getLogger().log(OutputController.Level.ERROR_ALL, DirectoryCheckResult.notDirMessage(f)); + } + result.isDir = false; + } + File testFile = null; + boolean correctPermissions = true; + try { + testFile = File.createTempFile("maindir", "check", f); + if (!testFile.exists()) { + correctPermissions = false; + } + try { + FileUtils.saveFile("ww", testFile); + String s = FileUtils.loadFileAsString(testFile); + if (!s.trim().equals("ww")) { + correctPermissions = false; + } + } catch (Exception ex) { + if (JNLPRuntime.isDebug()) { + ex.printStackTrace(); + } + correctPermissions = false; + } + File[] canList = f.listFiles(); + if (canList == null || canList.length == 0) { + correctPermissions = false; + } + testFile.delete(); + if (testFile.exists()) { + correctPermissions = false; + } else { + boolean created = testFile.mkdir(); + if (!created) { + correctPermissions = false; + } + if (testFile.exists()) { + if (testSubdir) { + DirectoryCheckResult subdirResult = testDir(testFile, verbose, false); + if (subdirResult.getFailures() != 0) { + result.subDir = subdirResult; + correctPermissions = false; + } + testFile.delete(); + if (testFile.exists()) { + correctPermissions = false; + } + } + } else { + correctPermissions = false; + } + } + } catch (Exception ex) { + if (JNLPRuntime.isDebug()) { + ex.printStackTrace(); + } + correctPermissions = false; + } finally { + if (testFile != null && testFile.exists()) { + testFile.delete(); + } + } + if (!correctPermissions) { + if (verbose) { + OutputController.getLogger().log(OutputController.Level.ERROR_ALL, DirectoryCheckResult.wrongPermissionsMessage(f)); + } + result.correctPermissions = false; + } + return result; + + } +} diff --git a/netx/net/sourceforge/jnlp/resources/Messages.properties b/netx/net/sourceforge/jnlp/resources/Messages.properties index 2e5fb82..15844e3 100644 --- a/netx/net/sourceforge/jnlp/resources/Messages.properties +++ b/netx/net/sourceforge/jnlp/resources/Messages.properties @@ -304,6 +304,9 @@ DCIncorrectValue=Property "{0}" has incorrect value "{1}". Possible values {2}. DCInternal=Internal error: {0} DCSourceInternal= DCUnknownSettingWithName=Property "{0}" is unknown. +DCmaindircheckNotexists=After all attempts, your configuration directory {0} do not exists. +DCmaindircheckNotdir=Your configuration directory {0} is not directory. +DCmaindircheckRwproblem=Your configuration directory {0} can not be read/written properly. # Value Validator messages. Messages should follow "Possible values ..." VVPossibleValues=Possible values {0} diff --git a/netx/net/sourceforge/jnlp/runtime/Boot.java b/netx/net/sourceforge/jnlp/runtime/Boot.java index 3f774cf..1ef0b6b 100644 --- a/netx/net/sourceforge/jnlp/runtime/Boot.java +++ b/netx/net/sourceforge/jnlp/runtime/Boot.java @@ -124,11 +124,15 @@ public final class Boot implements PrivilegedAction { * Launch the JNLP file specified by the command-line arguments. */ public static void main(String[] argsIn) { + args = argsIn; + if (AppContext.getAppContext() == null) { SunToolkit.createNewAppContext(); } + if (null != getOption("-headless")) + JNLPRuntime.setHeadless(true); + DeploymentConfiguration.move14AndOlderFilesTo15StructureCatched(); - args = argsIn; if (null != getOption("-viewer")) { @@ -170,9 +174,6 @@ public final class Boot implements PrivilegedAction { JNLPRuntime.setDefaultUpdatePolicy(new UpdatePolicy(value * 1000l)); } - if (null != getOption("-headless")) - JNLPRuntime.setHeadless(true); - if (null != getOption("-noupdate")) JNLPRuntime.setDefaultUpdatePolicy(UpdatePolicy.NEVER); -- cgit v1.2.3