001    /*
002     * Created by IntelliJ IDEA.
003     * User: d@hd.org
004     * Date: 27-May-02
005     * Time: 09:32:06
006    
007    Copyright (c) 1996-2012, Damon Hart-Davis
008    All rights reserved.
009    
010    Redistribution and use in source and binary forms, with or without
011    modification, are permitted provided that the following conditions are
012    met:
013    
014      * Redistributions of source code must retain the above copyright
015        notice, this list of conditions and the following disclaimer.
016    
017      * Redistributions in binary form must reproduce the above copyright
018        notice, this list of conditions and the following disclaimer in the
019        documentation and/or other materials provided with the
020        distribution.
021    
022    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
023    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
024    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
025    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
026    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
027    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
028    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
029    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
030    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
031    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
032    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
033    
034     */
035    package org.hd.d.pg2k.svrCore.props;
036    
037    // We try to depend as little as possible on other classes in the
038    // system as we need to initialise early during bootstrapping.
039    import java.io.BufferedInputStream;
040    import java.io.File;
041    import java.io.FileInputStream;
042    import java.io.IOException;
043    import java.io.InputStream;
044    import java.net.URI;
045    import java.net.URISyntaxException;
046    import java.security.MessageDigest;
047    import java.util.ArrayList;
048    import java.util.Collections;
049    import java.util.HashMap;
050    import java.util.HashSet;
051    import java.util.List;
052    import java.util.Map;
053    import java.util.Properties;
054    import java.util.Set;
055    
056    import javax.crypto.SecretKey;
057    
058    import org.hd.d.pg2k.svrCore.CoreConsts;
059    import org.hd.d.pg2k.svrCore.FileTools;
060    import org.hd.d.pg2k.svrCore.MemoryTools;
061    import org.hd.d.pg2k.svrCore.location.GeoUtils;
062    
063    import ORG.hd.d.IsDebug;
064    
065    /**This is a set of properties local to the JVM.
066     * This is the NEAR-minimum set of properties to do with security
067     * or local system configuration (such as the local layout
068     * of the file-system) that must not or cannot be sent over
069     * the network and shared between master and slaves.
070     * Much of this is bootstrapping data, such as the location of various
071     * configuration and persistent-state directories.
072     * Our aim is to minimise the need for other parts of the system
073     * to get at random JVM system properties and files, etc, where possible.
074     * <p>
075     * Some of the more sensitive data are made available
076     * via package-visible members so that the other props
077     * classes can use them but nothing else can see the values
078     * directly.
079     * <p>
080     * These properties are not Serializable and are never sent
081     * across the network (unlike GenProps), and have to be
082     * made available in a local file and/or system properties
083     * for example.
084     * (Do not include local properties file in the WAR or EAR file.)
085     * <p>
086     * These properties can be checked for being up-to-date
087     * from time to time, and the in-memory values will be
088     * refreshed if necessary.
089     * <p>
090     * Values set purely from system properties may be collected
091     * once at class creation and not again.
092     * <p>
093     * All access to values is via static methods
094     * appropriately synchronized to ensure thread-safety.
095     * <p>
096     * This is necessarily a rag-tag of values used all over the system,
097     * and will be loaded very early and should not depend on much of the
098     * rest of the system if at all.
099     */
100    public final class LocalProps
101        {
102        /**Name of cloud-mirror tag system property; non-null and non-empty.
103         * If the named system property is set to a valid mirror tag
104         * then this instance is a light-weight 'micro' cloud instance.
105         * <p>
106         * This is unqualified to help keep command-line parameters short.
107         */
108        public static final String PNAME_MICRO_CLOUD_TAG = "MicroTag";
109    
110        /**Validated value of micro tag, or null if not set or not syntactically valid. */
111        private static final String microTag;
112        static
113            {
114            String mt = null;
115            try { mt = validateMirrorTag(System.getProperty(PNAME_MICRO_CLOUD_TAG)); }
116            catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
117            microTag = mt;
118            }
119    
120        /**If true then this (WAR) instance is (permanently) configured as a light-weight cloud-hosted mirror.
121         * This may imply multiple behaviour and configuration changes throughout the system.
122         * <p>
123         * Implications of this being true are that CPU, bandwidth and file space may all be metered,
124         * and there may be no file access outside the WAR working cache.
125         * <p>
126         * Fast and non-blocking.
127         * <p>
128         * This state does not change for a running instance.
129         */
130        public static boolean isCloudMirrorInstance()
131            { return(null != microTag); }
132    
133        /**True if this instance should make no filesystem access outside the WAR cache. */
134        private static final boolean noGeneralFileAccess = LocalProps.isCloudMirrorInstance();
135    
136        /**True if this instance should make no filesystem access outside the WAR cache. */
137        public static boolean getNoGeneralFileAccess() { return(noGeneralFileAccess); }
138    
139        /**Veto attempted actual or implied file access with an exception if not permitted.
140         * @throws UnsupportedOperationException  to veto such attempted access
141         */
142        private static void vetoFileAccessIfNecessary()
143            {
144            if(noGeneralFileAccess) { throw new UnsupportedOperationException("light-weight mirror not permitted generic filesystem access"); }
145            }
146    
147        /**Minimum interval between rechecks of local properties file content (ms).
148         * Since much of our data comes from the filesystem and many filesystems'
149         * timestamp granularity is about one second, polling much faster than this
150         * is pointless and may simply end up blocking the caller and wasting
151         * CPU pointlessly.
152         * <p>
153         * (Arguably the only way that system properties can be changed is by
154         * programmatic intervention want so we don't want to recheck such values
155         * at all for fear of setting up a covert channel as well as just wasting
156         * CPU time.)
157         * <p>
158         * We pick a prime-ish retry interval to try to avoid collisions with other
159         * regular events.
160         * <p>
161         * A value of a few seconds to a few tens of seconds is probably appropriate.
162         */
163        private static final int MIN_RECHECK_MS = 33007;
164    
165        /**The last time that we checked/refreshed the data that we hold.
166         * We update this whenever we do a check whether we changed any values;
167         * this avoids possibly expensive and unnecessary rechecking.
168         * This value is private to us.
169         * <p>
170         * Initially zero to force immediate read on first use of these properties.
171         */
172        private static long lastRecheck;
173    
174        /**This routine rechecks and refreshes the data items if necessary.
175         * This is called internally by all getter methods to lazily ensure that
176         * data items are up to date.
177         * <p>
178         * When this has finished it sets lastRecheck and possibly timestamp;
179         * if this fails to complete the update neither (but especially lastRecheck)
180         * may be set.
181         * <p>
182         * When running as a light-weight mirror without filesystem access,
183         * any configuration information is limited to that statically available,
184         * mainly from system properties,
185         * and so this returns immediately.
186         */
187        private static synchronized void _recheck()
188            {
189            if(noGeneralFileAccess) { return; }
190    
191            final long now = System.currentTimeMillis();
192    
193            if(now - lastRecheck <= MIN_RECHECK_MS)
194                {
195                // Not time for a recheck yet...
196                return;
197                }
198    
199            try
200                {
201                // Get location of local properties file, if extant.
202                final File pf = new File(getLocalPropsFilename());
203    
204                final long fileTimestamp = pf.lastModified();
205    
206                // Warn user if file not found,
207                // and suggest what settings they might use.
208                if(fileTimestamp <= 0)
209                    {
210    System.err.println("WARNING: local properties file not found.");
211    System.err.println("  Set name in system property: " + PNAME_LOCAL_PROPS_FILENAME);
212    System.err.println("  or put file at: `" + DEFAULT_LOCAL_PROPS_FILENAME + "' .");
213                    }
214    
215                // Warn if the timestamp has gone backwards
216                // (or to zero because the file has disappeared).
217                if(fileTimestamp < timestamp)
218                    {
219                    System.err.println("WARNING: timestamp on local properties file has gone backwards or file has gone: " + pf);
220                    }
221    
222                // If the file has changed and is newer than our in-memory copy,
223                // read it in.
224                //
225                // If we have problems reading the info,
226                // we abort without updating any timestamps so that can try again
227                // immediately.
228                // We may have updated some of the properties...
229                if(fileTimestamp > timestamp)
230                    {
231                    Properties props; // The properties that we load.
232                    try {
233                        // Read the properties file in buffered chunks for efficiency.
234                        final InputStream is = new BufferedInputStream(
235                            new FileInputStream(pf));
236                        try { props = FileTools.loadProperties(is); }
237                        finally { is.close(); } // Release the handle ASAP...
238                        }
239                    catch(final IOException e)
240                        {
241                        // If we encounter an I/O problem,
242                        // log it to System.err,
243                        // and return without updating our timestamps.
244                        e.printStackTrace();
245                        return;
246                        }
247    
248                    // Temporaries to help in parsing.
249                    int iTmp;
250                    long lTmp;
251                    String sTmp;
252    
253                    // Get dir for read-only configuration files.
254                    confDir = MemoryTools.intern(props.getProperty(PNAME_CONF_DIR));
255    
256                    // Get dir for data files.
257                    dataDir = MemoryTools.intern(props.getProperty(PNAME_DATA_DIR));
258    
259                    // Get dir for cache files.
260                    cacheDir = MemoryTools.intern(props.getProperty(PNAME_CACHE_DIR));
261    
262    if(IsDebug.isDebug) { System.out.println("Reloaded local properties ("+pf+") with confdir "+confDir+" and dataDir "+dataDir); }
263    
264                    // Get name of simple password file.
265                    simplePassfile = MemoryTools.intern(props.getProperty(PNAME_SIMPLEPASS_FILE));
266    
267                    // Get name of upload directory.
268                    uploadDir = MemoryTools.intern(props.getProperty(PNAME_UPLOAD_DIR));
269    
270                    // Get name of persistent-state dir.
271                    persistentStateDirFromSystemProperties = MemoryTools.intern(props.getProperty(PNAME_PERSISTENT_STATE_DIR));
272    
273                    // Get mirror tag.
274                    // Check thoroughly for syntactic validity before assigning.
275                    // If invalid, then set to null instead.
276                    final String putativeMirrorTag = props.getProperty(PNAME_MIRROR_TAG);
277                    sTmp = validateMirrorTag(putativeMirrorTag);
278                    mirrorTag = MemoryTools.intern(sTmp);
279    
280    
281                    iTmp = -1;
282                    try { iTmp = Integer.parseInt(props.getProperty(PNAME_MAX_UPLOAD_TOTAL_BYTES, "10123456"), 10); }
283                    catch(final Exception e) { } // Ignore errors.
284                    uploadMaxBytesTotal = iTmp;
285    
286                    iTmp = -1;
287                    try { iTmp = Integer.parseInt(props.getProperty(PNAME_MAX_UPLOAD_PER_USER_BYTES, "10123456"), 10); }
288                    catch(final Exception e) { } // Ignore errors.
289                    uploadMaxBytesPerUser = iTmp;
290    
291                    lTmp = -1;
292                    try { lTmp = Long.parseLong(props.getProperty(PNAME_WEBSVR_MAX_EX_CACHE_BYTES, String.valueOf(SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE)), 10); }
293                    catch(final Exception e) { } // Ignore errors.
294                    WEBSVR_MAX_EX_CACHE_BYTES = lTmp;
295    
296                    iTmp = -1;
297                    try { iTmp = Integer.parseInt(props.getProperty(PNAME_SERVER_SLOWDOWN_FACTOR, "1"), 10); }
298                    catch(final Exception e) { } // Ignore errors.
299                    SERVER_SLOWDOWN_FACTOR = iTmp;
300    
301                    // Get raw data protection key, if any.
302                    // Any system property takes precedence over a value from the file.
303                    sTmp = props.getProperty(PNAME_XFER_HMAC_KEY);
304                    try { sTmp = System.getProperty(PNAME_XFER_HMAC_KEY, sTmp); }
305                    catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
306                    final SecretKey putativeXferKey = processXferKey(sTmp);
307                    xferHMACKey = putativeXferKey;
308    
309                    // Parse the zero-or-more low-power filesystem warning flag file names.
310                    sTmp = props.getProperty(PNAME_LOW_POWER_FILE_FLAGS);
311                    if(sTmp == null) { lowerPowerFileFlags = Collections.emptyList(); }
312                    else
313                        {
314                        // Separate filenames with whitespace.
315                        final String names[] = sTmp.split("[ \t\r\n]+");
316                        final List<File> result = new ArrayList<File>(names.length);
317                        // Non-absolute pathnames are relative to the localprops dir.
318                        for(final String name : names)
319                            {
320                            File f = new File(name);
321                            if(!f.isAbsolute())
322                                {
323                                final File lpDir = new File(getLocalPropsFilename()).getParentFile();
324                                f = (new File(lpDir, name)).getAbsoluteFile();
325                                }
326                            result.add(f);
327                            }
328                        // Make unmodifiable result.
329                        lowerPowerFileFlags = Collections.unmodifiableList(result);
330                        }
331    
332                    // Parse the zero-or-more low-power flag URL names.
333                    sTmp = props.getProperty(PNAME_LOW_POWER_FILE_URIS);
334                    if(sTmp == null) { lowerPowerFileURIs = Collections.emptySet(); }
335                    // FIXME
336                    else
337                        {
338                        // Separate URIs with whitespace.
339                        final String names[] = sTmp.split("[ \t\r\n]+");
340                        final Set<URI> result = new HashSet<URI>(1 + names.length*2);
341                        for(final String name : names)
342                            {
343                            try { result.add(new URI(name)); }
344                            catch(final URISyntaxException e) { /* Silently discard bad URIs. */ }
345                            }
346                        // Make unmodifiable result.
347                        lowerPowerFileURIs = Collections.unmodifiableSet(result);
348                        }
349    
350                    // Parse/load CPU load thresholds.
351                    sTmp = props.getProperty(PNAME_LIGHT_LOAD_FRACTION_MAX);
352                    if(sTmp != null)
353                        {
354                        try
355                            {
356                            final float f = Float.parseFloat(sTmp);
357                            // Must be in range ]0,1[ and thus also not Inf/NaN.
358                            if((f > 0) && (f < 1)) { lightLoadMax = new Float(f); }
359                            }
360                        catch(final NumberFormatException e) { }
361                        }
362                    sTmp = props.getProperty(PNAME_HEAVY_LOAD_FRACTION_MIN);
363                    if(sTmp != null)
364                        {
365                        try
366                            {
367                            final float f = Float.parseFloat(sTmp);
368                            // Must be greater than getLightLoadMax() and also not Inf/NaN.
369                            if(f > 0) { heavyLoadMin = new Float(f); }
370                            }
371                        catch(final NumberFormatException e) { }
372                        }
373    
374                    // Extract the generic key/value properties, if any.
375                    final Map<String,String> g = new HashMap<String, String>();
376                    for(final Object keyO : props.keySet())
377                        {
378                        if(!(keyO instanceof String))
379                            { throw new IllegalArgumentException("bad properties key: not String"); }
380                        final String key = (String) keyO;
381    
382                        if(!key.startsWith(GEN_PREFIX)) { continue; }
383    
384                        final String gKey = key.substring(GEN_PREFIX.length());
385                        final String gVal = props.getProperty(key);
386    
387                        // Validate the values later.
388                        g.put(gKey, gVal);
389                        }
390                    // If no generic keys then save space with shared empty Map.
391                    if(g.size() == 0)
392                        { gen = Collections.emptyMap(); }
393                    else
394                        { gen = Collections.unmodifiableMap(g); }
395                    }
396    
397                // Save the timestamp from the properties file
398                // having successfully completed all that we wanted to.
399                timestamp = fileTimestamp;
400    
401                // Update completed.
402                lastRecheck = now;
403                }
404            catch(final SecurityException e)
405                {
406                if(!reportedSecurityException)
407                    {
408                    System.err.println("NOTE: no access to local properties file: " + e.getMessage());
409                    reportedSecurityException = true;
410                    }
411    
412                // Quietly absorb the security/permissions error,
413                // and defer retrying for the normal length of time.
414                lastRecheck = now;
415                }
416            }
417    
418        /**Return computed SecretKey from raw text, or null if none/invalid. */
419        private static SecretKey processXferKey(String rawKeyText)
420            {
421            if(rawKeyText == null) { return(null); }
422            else
423                {
424                rawKeyText = rawKeyText.trim();
425                if(rawKeyText.length() < 16)
426                    {
427                    System.err.println("[WARNING: too-short xfer key ignored.]");
428                    return(null);
429                    }
430                else
431                    {
432                    // OK, key looks superficially OK,
433                    // so try to turn it into something usable for HMAC in our tunnel.
434                    try
435                        {
436                        // Extract an entropy-dense form of the (trimmed) key supplied,
437                        // ie with all of the goodness, but fewer bytes to process each time.
438                        final byte hashed[] = MessageDigest.getInstance(CoreConsts.HASH_SHA1).
439                            digest(rawKeyText.getBytes("UTF-8"));
440                        // Home-brew immutable/tamper-resistant secret key.
441                        return(new SecretKey(){
442                            private static final long serialVersionUID = 3350400258615302664L;
443                            public String getAlgorithm()    { return(""); }
444                            public byte[] getEncoded()      { return(hashed.clone()); }
445                            public String getFormat()       { return("RAW"); }
446                            });
447                        }
448                    catch(final Exception e)
449                        {
450                        System.err.println("[ERROR: LocalProps: unable to create/convert xfer key.]");
451                        e.printStackTrace();
452                        return(null);
453                        }
454                    }
455                }
456            }
457    
458        /**Validate putative mirror tag; returns non-null only if input is syntactically valid and non-null. */
459        private static String validateMirrorTag(String putativeMirrorTag)
460            {
461            // Reject invalid tags by returning a null.
462            if((putativeMirrorTag == null) || (putativeMirrorTag.length() < 4) || (putativeMirrorTag.length() > 255))
463                { putativeMirrorTag = null; }
464            // Check that the cc is valid; return null if not.
465            else if(!GeoUtils.CCTLD.isSyntaticallyValidCcTLD(putativeMirrorTag.substring(0, 2)))
466                { putativeMirrorTag = null; }
467            // Check that the dash is present; return null if not.
468            else if(putativeMirrorTag.charAt(2) != '-')
469                { putativeMirrorTag = null; }
470            // Check that the remainder of the tag is lower-case or digits; return null if not.
471            else
472                {
473                for(int i = putativeMirrorTag.length(); --i >= 3; )
474                    {
475                    final char c = putativeMirrorTag.charAt(i);
476                    if((c >= 'a') && (c <= 'z')) { continue; }
477                    if((c >= '0') && (c <= '9')) { continue; }
478                    putativeMirrorTag = null;; // Bad character found so don't use this...
479                    }
480                }
481            return(putativeMirrorTag);
482            }
483    
484        /**Set true if we've noted a SecurityException trying to read LocalProperties.
485         * Marked volatile for lock-free thread-safe access.
486         */
487        private static volatile boolean reportedSecurityException;
488    
489        /**The last time our loaded values changed.
490         * This can be used to cascade changes to parts of the system that depend
491         * on these LocalProps.
492         * <p>
493         * Will never be newer than lastRecheck.
494         * <p>
495         * Initially zero, to indicate no (file-based) values loaded.
496         * <p>
497         * The timestamp is the latest timestamp of any of the files read to
498         * make up the dataset.
499         */
500        private static long timestamp;
501    
502        /**Returns the timestamp of the current set of data values.
503         * Forces a (re)load/refresh of data if necessary.
504         * Zero indicates that no (file-based) values have (yet) been loaded.
505         * <p>
506         * The timestamp is the latest timestamp of any of the files read to
507         * make up the dataset.
508         */
509        public static synchronized long getTimestamp()
510            { _recheck(); return(timestamp); }
511    
512        /**Default suffix of local properties files, including the dot. */
513        public static final String DEFAULT_PROPS_SUFFIX = ".properties";
514        /**Default filename of local properties file. */
515        public static final String DEFAULT_LOCAL_PROPS_FILENAME =
516            "local" + DEFAULT_PROPS_SUFFIX;
517        /**Name of props file for local config information property. */
518        public static final String PNAME_LOCAL_PROPS_FILENAME = "org.hd.d.pg2k.localPropsFilename";
519        /**Cache of name of props file for local config information; null if no explicit system property value set. */
520        private static final String localPropsFilenameFromSystemProperties;
521        /**Initialise localPropsFilenameFromSystemProperties. */
522        static
523            {
524            String s = null;
525            try { s = System.getProperty(PNAME_LOCAL_PROPS_FILENAME); }
526            catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
527            localPropsFilenameFromSystemProperties = s;
528            }
529        /**Name of props file for local config information; never null.
530         * It may be difficult for the system to run without this parameter
531         * set correctly or if the corresponding file is not correctly set up.
532         * <p>
533         * This will specify a file in the current working directory if none
534         * explicitly specified, and will deliver a warning if it cannot be found.
535         * <p>
536         * This might be bad news if the current working directory changes
537         * or is inappropriate or unhelpful, so set the system property explicitly
538         * if this is the case.
539         * <p>
540         * This does not check that the specified file actually exists.
541         * <p>
542         * We only use this internally; for example this may be inefficient to
543         * continually recheck and we don't necessarily want to publicise the
544         * actual location more than we must!
545         *
546         * @throws UnsupportedOperationException  if this is a light-weight mirror
547         *     and thus without general filesystem access
548         */
549        private static synchronized String getLocalPropsFilename()
550            {
551            vetoFileAccessIfNecessary();
552    
553            // Use explicit value if set.
554            String result = localPropsFilenameFromSystemProperties;
555            // Else try to use canonicalised default name relative to pwd.
556            if(result == null)
557                {
558                try {
559                    result = (new File(DEFAULT_LOCAL_PROPS_FILENAME)).getCanonicalPath();
560                    }
561                catch(final IOException e)
562                    {
563                    System.err.println("WARNING: cannot canonicalise local properties filename " + DEFAULT_LOCAL_PROPS_FILENAME);
564                    }
565                }
566            // Else try to use relative name directly.
567            if(result == null)
568                { result = DEFAULT_LOCAL_PROPS_FILENAME; }
569            return(result);
570            }
571    
572        /**Name of local properties parameter for name of subdirectory for (mainly read-only) data files. */
573        public static final String PNAME_DATA_DIR = "pg2k.data.dir";
574        /**Local cache of name of subdirectory for data files. */
575        private static String dataDir;
576        /**Get name of subdirectory for data files; never null.
577         * If the PNAME_DATA_DIR local property is set then it is used,
578         * else the CoreConsts.FS_DATA_ROOT value is used.
579         * <p>
580         * The returned value is not necessarily canonical
581         * to avoid prodding/waking any filesystems unnecessarily.
582         *
583         * @throws UnsupportedOperationException  if this is a light-weight mirror
584         *     and thus without general filesystem access
585         */
586        public static synchronized String getDataDir()
587            {
588            vetoFileAccessIfNecessary();
589    
590            // Force properties reload if necessary.
591            _recheck();
592    
593            // Use local property if set, ...
594            String result = dataDir;
595            // ... else use default from CoreConsts.
596            if(result == null) { result = CoreConsts.FS_DATA_ROOT; }
597    
598            // If the directory name is not absolute,
599            // make it relative to the directory that the local properties are in.
600            if(!(new File(result)).isAbsolute())
601                {
602                final File lpDir = new File(getLocalPropsFilename()).getParentFile();
603                result = (new File(lpDir, result)).getAbsolutePath();
604                }
605    
606            // Do not canonicalise the path
607            // since doing so may actually force access to the data filesystem
608            // eg mounting and powering up the storage.
609    //        // In any case, try to canonicalise the path before returning it.
610    //        try { result = (new File(result)).getCanonicalPath(); }
611    //        catch(final IOException e) { } // In case of error use non-canonical form...
612    
613            return(result);
614            }
615    
616        /**Name of local properties parameter for name of subdirectory for read-only conf files. */
617        public static final String PNAME_CONF_DIR = "pg2k.config.dir";
618        /**Local cache of name of subdirectory for read-only conf files. */
619        private static String confDir;
620        /**Get name of subdirectory for read-only conf files; never null.
621         * If the PNAME_CONF_DIR local property is set then it is used,
622         * else the CoreConsts.FS_CONF_ROOT value is used, canonicalised
623         * if possible to give more meaningful error message at least.
624         *
625         * @throws UnsupportedOperationException  if this is a light-weight mirror
626         *     and thus without general filesystem access
627         */
628        public static synchronized String getConfDir()
629            {
630            vetoFileAccessIfNecessary();
631    
632            // Force properties reload if necessary.
633            _recheck();
634    
635            // Use local property if set, ...
636            String result = confDir;
637            // ... else use default from CoreConsts.
638            if(result == null) { result = CoreConsts.FS_CONF_ROOT; }
639    
640            // If the directory name is not absolute,
641            // make it relative to the directory that the local properties are in.
642            if(!(new File(result)).isAbsolute())
643                {
644                final File lpDir = new File(getLocalPropsFilename()).getParentFile();
645                result = (new File(lpDir, result)).getAbsolutePath();
646                }
647    
648            // In any case, try to canonicalise the path before returning it.
649            try { result = (new File(result)).getCanonicalPath(); }
650            catch(final IOException e) { } // In case of error use non-canonical form...
651    
652            return(result);
653            }
654        
655    
656        /**Name of local properties parameter for name of subdirectory for cached data files. */
657        public static final String PNAME_CACHE_DIR = "pg2k.cache.dir";
658        /**Local cache of name of subdirectory for cached data files. */
659        private static String cacheDir;
660        /**Get name of subdirectory for cached files; null if WAR cache default should be used.
661         * If the PNAME_DATA_DIR local property is set then it is used,
662         * <p>
663         * The returned value is not necessarily canonical
664         * to avoid prodding/waking any filesystems unnecessarily.
665         *
666         * @throws UnsupportedOperationException  if this is a light-weight mirror
667         *     and thus without general filesystem access
668         */
669        public static synchronized String getCacheDir()
670            {
671            vetoFileAccessIfNecessary();
672    
673            // Force properties reload if necessary.
674            _recheck();
675    
676            // Use local property if set, ...
677            String result = cacheDir;
678            // ... else use default from CoreConsts.
679            if(result == null) { return(null); }
680    
681            // If the directory name is not absolute,
682            // make it relative to the directory that the local properties are in.
683            if(!(new File(result)).isAbsolute())
684                {
685                final File lpDir = new File(getLocalPropsFilename()).getParentFile();
686                result = (new File(lpDir, result)).getAbsolutePath();
687                }
688    
689            return(result);
690            }
691    
692    
693    
694        /**Name of local properties parameter for name of simple password file. */
695        public static final String PNAME_SIMPLEPASS_FILE =
696            "pg2k.upload.simplepass.file";
697        /**Local cache of name of simple password file; null if none. */
698        private static String simplePassfile;
699        /**Get name of simple password file; null if none.
700         * There is no default for this; if it is not set
701         * then there will be no simple passwords.
702         * <p>
703         * If the path supplied is not absolute,
704         * it is made relative to the result of getConfDir().
705         */
706        public static synchronized String getSimplePassfile()
707            {
708            vetoFileAccessIfNecessary();
709    
710            // Force properties reload if necessary.
711            _recheck();
712    
713            // Use local property if set, ...
714            String result = simplePassfile;
715            // ... else return null.
716            if(result == null) { return(null); }
717    
718            // If the directory name is not absolute,
719            // make it relative to the result of getConfDir().
720            if(!(new File(result)).isAbsolute())
721                {
722                final File confDir = new File(getConfDir());
723                result = (new File(confDir, result)).getAbsolutePath();
724                }
725    
726            // In any case, try to canonicalise the path before returning it.
727            try { result = (new File(result)).getCanonicalPath(); }
728            catch(final IOException e) { } // In case of error use non-canonical form...
729    
730            return(result);
731            }
732    
733    
734        /**Name of local properties parameter for name of upload dir. */
735        public static final String PNAME_UPLOAD_DIR = "pg2k.upload.dir";
736        /**Local cache of name of upload dir; null if none specified. */
737        private static String uploadDir;
738        /**Get name of upload dir or null if none was specified.
739         * If the PNAME_UPLOAD_DIR local property is set then it is used.
740         * <p>
741         * If a directory is specified but is not absolute,
742         * it is taken to be relative to the directory the local properties are in.
743         * <p>
744         * Warns if the specified directory is not present,
745         * but returns the path anyway if a path was specified;
746         * the caller should check if it is usable.
747         *
748         * @throws UnsupportedOperationException  if this is a light-weight mirror
749         *     and thus without general filesystem access
750         */
751        public static synchronized String getUploadDir()
752            {
753            vetoFileAccessIfNecessary();
754    
755            // Force properties reload if necessary.
756            _recheck();
757    
758            // Use local property if set.
759            String result = uploadDir;
760    
761            if(result != null)
762                {
763                // If the directory name is not absolute,
764                // make it relative to the directory the local properties are in.
765                if(!(new File(result)).isAbsolute())
766                    {
767                    final File lpDir = new File(getLocalPropsFilename()).getParentFile();
768                    result = (new File(lpDir, result)).getAbsolutePath();
769                    }
770    
771                // In any case, try to canonicalise the path before returning it.
772                try { result = (new File(result)).getCanonicalPath(); }
773                catch(final IOException e) { } // In case of error use non-canonical form...
774    
775                if(!(new File(result)).isDirectory())
776                    {
777                    System.err.println("WARNING: upload directory not present: " + result);
778                    }
779                }
780    
781            return(result);
782            }
783    
784    
785        /**Name of Web server maximum-upload-space-per-user (bytes) property. */
786        public static final String PNAME_MAX_UPLOAD_PER_USER_BYTES = "pg2k.upload.maxBytes.perUser";
787        /**Web server maximum-upload-space-per-user (bytes). */
788        private static int uploadMaxBytesPerUser;
789        /**Get the Web server maximum-upload-space-per-user (bytes).
790         * Constrained to the range 0 to 1GB, default approx 10MB.
791         */
792        public static synchronized int getUploadMaxBytesPerUser()
793            { return(Math.max(0, Math.min(1123456789, uploadMaxBytesPerUser))); }
794    
795        /**Name of Web server maximum-upload-space-total (bytes) property. */
796        public static final String PNAME_MAX_UPLOAD_TOTAL_BYTES = "pg2k.upload.maxBytes.total";
797        /**Web server maximum-upload-space-total (bytes). */
798        private static int uploadMaxBytesTotal;
799        /**Get the Web server maximum-upload-space-total (bytes).
800         * Constrained to the range 0 to 1GB, default approx 10MB.
801         * Also constrained to be no smaller than uploadMaxBytesPerUser.
802         */
803        public static synchronized int getUploadMaxBytesTotal()
804            { return(Math.max(getUploadMaxBytesPerUser(), Math.min(1123456789, uploadMaxBytesTotal))); }
805    
806    
807    
808    
809        /**Name of local directory for store of persistent state property. */
810        public static final String PNAME_PERSISTENT_STATE_DIR =
811            "org.hd.d.pg2k.persistentStateDir";
812        /**Local cache of name of local persistent-state directory; null if none. */
813        private static String persistentStateDirFromSystemProperties;
814        /**Relative path from local properties directory to persistent state dir. */
815        public static final String DEFAULT_STATE_DIR = "persistentstate.dat";
816        /**Get name of directory for storing local persistent state; never null.
817         * This is used for storing (especially valuable) data that should survive
818         * over many runs of the WAR/J2EE, but which is local to each WAR instance,
819         * such as usage stats, though may also be used by the master instance to
820         * persist user login data for example.
821         * <p>
822         * If the system property is not set, then we take this relative to the
823         * directory in which we look for the local properties.
824         * <p>
825         * Warns if the specified directory is not present.
826         *
827         * @throws UnsupportedOperationException  if this is a light-weight mirror
828         *     and thus without general filesystem access
829         */
830        public final static synchronized String getPersistentStateDir()
831            {
832            vetoFileAccessIfNecessary();
833    
834            // Force properties reload if necessary.
835            _recheck();
836    
837            // Use explicit value if set.
838            String result = persistentStateDirFromSystemProperties;
839    
840            // Else try to use relative name directly.
841            if(result == null) { result = DEFAULT_STATE_DIR; }
842    
843    
844            // If the directory name is not absolute,
845            // make it relative to the directory the local properties are in.
846            if(!(new File(result)).isAbsolute())
847                {
848                final File lpDir = new File(getLocalPropsFilename()).getParentFile();
849                result = (new File(lpDir, result)).getAbsolutePath();
850                }
851    
852            // In any case, try to canonicalise the path before returning it.
853            try { result = (new File(result)).getCanonicalPath(); }
854            catch(final IOException e) { } // In case of error use non-canonical form...
855    
856    //        if(!(new File(result)).isDirectory())
857    //            {
858    //            System.err.println("WARNING: persistent state directory not present: " + result);
859    //            }
860            return(result);
861            }
862    
863    
864        /**Name of mirror-tag property. */
865        public static final String PNAME_MIRROR_TAG =
866            "org.hd.d.pg2k.mirrorTag";
867        /**Local cache of mirror tag (micro tag if set); null if none. */
868        private static String mirrorTag = microTag;
869        /**Get valid "cc-xxxx" mirror-tag name for this host; null if none.
870         * If this host is a mirror, it has a tag of the form <samp>cc-xxxxx</samp>
871         * where "cc" is a two-letter country code
872         * (as in a top-level ccTLD domain name, eg "uk" rather than "gb")
873         * where the host resides for Internet connectivity purposes,
874         * and "xxxx" is an alphanumeric tag indicating a particular host,
875         * and the whole string is less than 256 characters.
876         * <p>
877         * The entire tag is a lower-case printable ASCII (no whitespace)
878         * alphanumeric value.
879         * <p>
880         * This host should be reachable by the full name of
881         * "mirror-cc-xxxx.gallery.hd.org"
882         * for a tag of "cc-xxxx" and a main domain name of "gallery.hd.org".
883         * <p>
884         * This routine will not return an invalid/unsafe tag.
885         * <p>
886         * This routine does NOT attempt any DNS lookups or in other ways
887         * attempt to verify that the full mirror host name exists or is valid.
888         */
889        public final static synchronized String getMirrorTag()
890            {
891            // Force properties reload if necessary.
892            _recheck();
893    
894            return(mirrorTag);
895            }
896    
897    
898    
899        /**Name of local directory for store of thumbnail data property. */
900        public static final String PNAME_PERSISTENT_THUMBNAIL_DIR =
901            "org.hd.d.pg2k.thumbnailDir";
902        /**Local cache of name of local thumbnail directory; null if none. */
903        private static final String thumbnailDirFromSystemProperties;
904        /**Initialise thumbnailDirFromSystemProperties. */
905        static
906            {
907            String s = null;
908            try { s = System.getProperty(PNAME_PERSISTENT_THUMBNAIL_DIR); }
909            catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
910            thumbnailDirFromSystemProperties = s;
911            }
912        /**Relative path from exhibit directory to thumbnails dir. */
913        public static final String DEFAULT_THUMBNAIL_DIR = "../_tn";
914        /**Get name of directory for storing off-line master thumbnails; never null.
915         * This is used for storing off-line computed thumbnails that should survive
916         * over many runs of the WAR/J2EE, mainly intended for read-only
917         * access by the master server, and write access by an off-line tool
918         * to create thumbnails in a less restrictive environment than the WAS.
919         * <p>
920         * If the system property is not set, then we take this relative to the
921         * directory in which we look for the exhibits.
922         * <p>
923         * If the property is set to "." then the exhibit directory is used.
924         */
925        public final static synchronized String getThumbnailRelDir()
926            {
927            vetoFileAccessIfNecessary();
928    
929            // Use explicit value if set.
930            String result = thumbnailDirFromSystemProperties;
931            // Else try to use relative name directly.
932            if(result == null)
933                {
934                result = DEFAULT_THUMBNAIL_DIR;
935                }
936            return(result);
937            }
938    
939    
940        /**Name of Web server maximum generic exhibit cache size (64-bit value, bytes). */
941        public static final String PNAME_WEBSVR_MAX_EX_CACHE_BYTES = "pg2k.websvr.ex.maxcachesize";
942        /**System-property value for maximum generic exhibit cache size; ~4GB default if unset. */
943        private static final long SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE;
944        /**Initialise SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE. */
945        static
946            {
947            long mcb = 1L << 32;
948            try { mcb = Long.getLong(PNAME_WEBSVR_MAX_EX_CACHE_BYTES, mcb); }
949            catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
950            SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE = mcb;
951            }
952        /**Web server maximum generic exhibit cache size (64-bit value, bytes). */
953        private static long WEBSVR_MAX_EX_CACHE_BYTES = SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE; // 4GB default; about 2^16 thumbnails @ 2^16 bytes each.
954        /**Get the Web server maximum generic exhibit cache size (64-bit value, bytes); non-negative.
955         * Constrained to the range approx 0 to 1TB, default approx 2GB.
956         * <p>
957         * This value can be specified with a system property of the same name
958         * if no value is supplied in the properties file.
959         */
960        public static synchronized long getWEBSVR_MAX_EX_CACHE_BYTES()
961            {
962            _recheck();
963    
964            // Return constrained value.
965            return(Math.max(0L, Math.min(1123456123456L, WEBSVR_MAX_EX_CACHE_BYTES)));
966            }
967    
968    
969        /**Name of server slowdown factor; strictly positive. */
970        public static final String PNAME_SERVER_SLOWDOWN_FACTOR = "org.hd.d.pg2k.serverSlowdownFactor";
971        /**Server slowdown factor; defaults to 1 except for cloud (CPU-sensitive) environment where it is higher. */
972        private static int SERVER_SLOWDOWN_FACTOR = isCloudMirrorInstance() ? 2 : 1;
973        /**Get server-slowdown-factor; strictly positive.
974         * Defaults to 1, and no more than about, say, 100.
975         * <p>
976         * Is a factor by which the server under-reports its available capacity, etc.
977         * <p>
978         * When &gt; 1 then this has the effect of:
979         * <ul>
980         * <li>reducing aggressiveness of precaching,
981         * <li>reducing reported available bandwidth,
982         *     (and thus the chance of being picked as a user's "preferred" mirror)
983         * </ul>
984         * etc, for sites with constrained or expensive or non-dedicated bandwidth.
985         */
986        public static synchronized int getServerSlowdownFactor()
987            {
988            _recheck();
989    
990            // Coerce into reasonable range.
991            return(Math.max(1, Math.min(100, SERVER_SLOWDOWN_FACTOR)));
992            }
993    
994    
995        /**Name of local properties parameter for HMAC key used to protect data in transit. */
996        public static final String PNAME_XFER_HMAC_KEY = "org.hd.d.pg2k.xferkey";
997        /**Local cache of immutable HMAC (secret) key used to protect data in transit; null if none. */
998        private static SecretKey xferHMACKey;
999        /**Initialise xferHMACKey safely. */
1000        static
1001            {
1002            SecretKey xk = null;
1003            try { xk = processXferKey(System.getProperty(PNAME_XFER_HMAC_KEY)); }
1004            catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
1005            xferHMACKey = xk;
1006            }
1007        /**Get HMAC (secret) key used to protect data in transit; null if none.
1008         * There is no default for this;
1009         * if it is not set then there will be no data protection key.
1010         * <p>
1011         * This first attempts to initialised from the system property
1012         * then any value in the local properties file overrides this.
1013         * <p>
1014         * The key returned is immutable.
1015         * <p>
1016         * The key should be handled with care, since it is sensitive data.
1017         */
1018        public static synchronized SecretKey getXferHMACKey()
1019            {
1020            // Force properties reload if necessary.
1021            _recheck();
1022    
1023            // Use local property if set.
1024            return(xferHMACKey);
1025            }
1026    
1027        /**Returns true if there is an xfer key available, false otherwise. */
1028        public static boolean hasXferKey() { return(null != getXferHMACKey()); }
1029    
1030    
1031        /**Name of local properties parameter for Set of low-power file-system flags. */
1032        public static final String PNAME_LOW_POWER_FILE_FLAGS =
1033            "pg2k.lowerPowerFileFlags";
1034        /**Local cache of immutable Set of low-power file-system flags; empty if none. */
1035        private static List<File> lowerPowerFileFlags = Collections.emptyList();
1036        /**Get immutable List of low-power file-system flags; empty if none.
1037         * Will not contain duplicates or nulls.
1038         * <p>
1039         * If more than one entry, the last indicates extreme low-power mode.
1040         * <p>
1041         * The value returned is immutable.
1042         */
1043        public static synchronized List<File> getLowerPowerFileFlags()
1044            {
1045            // Force properties reload if necessary.
1046            _recheck();
1047    
1048            // Use local property if set.
1049            return(lowerPowerFileFlags);
1050            }
1051    
1052    
1053        /**Name of local properties parameter for Set of low-power remote URI http: or https: flags.
1054         * Should only be regarded as absent, ie not low-power mode, if a 404 is obtained.
1055         */
1056        public static final String PNAME_LOW_POWER_FILE_URIS =
1057            "pg2k.lowerPowerURIFlags";
1058        /**Local cache of immutable Set of low-power file-system flags; empty if none. */
1059        private static Set<URI> lowerPowerFileURIs = Collections.emptySet();
1060        /**Get immutable Set of low-power file-system URIs; empty if none.
1061         * The value returned is immutable.
1062         */
1063        public static synchronized Set<URI> getLowerPowerFileURIs()
1064            {
1065            // Force properties reload if necessary.
1066            _recheck();
1067    
1068            // Use local property if set.
1069            return(lowerPowerFileURIs);
1070            }
1071    
1072    
1073        /**Default max fraction of available CPUs in use for system to be considered lightly loaded; positive range ]0.0,1.0[ exclusive.
1074         * Set threshold to allow for functional units possibly being shared (eg HyperThreading),
1075         * and thus different systems have different notions of 'processor'
1076         * for the purposes of our particular workload.
1077         * <p>
1078         * Also avoid significant extra heating or power draw.
1079         * <p>
1080         * A value of between 0.1 and (say) 0.75 is probably reasonable.
1081         */
1082        public static final float DEFAULT_LIGHT_LOAD_FRACTION_MAX = isCloudMirrorInstance() ? 0.1f : 0.45f;
1083    
1084        /**Name of CPU light-load threshold. */
1085        public static final String PNAME_LIGHT_LOAD_FRACTION_MAX = "org.hd.d.pg2k.CPU.lightLoadMax";
1086    
1087        /**CPU light-load threshold; can be null if none set. */
1088        private static /* final */ Float lightLoadMax;
1089    
1090        /**Get CPU light-load threshold; range ]0.0,1.0[ exclusive. */
1091        public static final float getLightLoadMax()
1092            {
1093            _recheck();
1094    
1095            final Float ll = lightLoadMax;
1096            final float result = (ll == null) ? DEFAULT_LIGHT_LOAD_FRACTION_MAX : ll;
1097            assert((result > 0) && (result < 1));
1098            return(result);
1099            }
1100    
1101        /**Min fraction of available CPUs in use for system to be considered very heavily loaded; strictly positive.
1102         * Can be more than 1.0 indicating queued work for each available CPU,
1103         * and maybe much higher for a usually/acceptably disc-I/O-heavy environment.
1104         * <p>
1105         * Note that there may be immediate visual effects on the Web site of exceeding this,
1106         * such as an obviously-'lite' UI to reduce load,
1107         * and thus doing so prematurely may be annoying to users.
1108         * <p>
1109         * Must be higher than DEFAULT_LIGHT_LOAD_FRACTION_MAX to avoid confusion,
1110         * and this can be easily achieved by having a value greater than 1.0.
1111         * <p>
1112         * Keeping this significantly below 1 will not soak up all available CPU cycles
1113         * but may result in better interactivity/responsiveness.
1114         * <p>
1115         * Note also that on environment such as AWS as at 2011/05
1116         * the value returned by this does not accurately indicate spare CPU
1117         * available to *this* virtual instance (eg may be underlying host's uptime)
1118         * so this should be conservative to help compensate in such environments.
1119         * <p>
1120         * A value of between 0.25 and (say) 5.0 is probably reasonable.
1121         */
1122        public static final float DEFAULT_HEAVY_LOAD_FRACTION_MIN = isCloudMirrorInstance() ? 0.4f : 0.9f;
1123    
1124        /**Name of CPU heavy-load threshold. */
1125        public static final String PNAME_HEAVY_LOAD_FRACTION_MIN  = "org.hd.d.pg2k.CPU.heavyLoadMin";
1126    
1127        /**CPU heavy-load threshold; can be null if none set. */
1128        private static /* final */ Float heavyLoadMin;
1129    
1130        /**Get heavy-load threshold; strictly positive and always higher than the light-load-threshold. */
1131        public static float getHeavyLoadMin()
1132            {
1133            _recheck();
1134    
1135            final Float hl = heavyLoadMin;
1136            final float result = Math.max((hl == null) ? DEFAULT_HEAVY_LOAD_FRACTION_MIN : hl,
1137                    getLightLoadMax() + 0.25f); // Always a little larger than the light-load threshold.
1138            assert(result > 0);
1139            return(result);
1140            }
1141    
1142        /**If true then this instance should run in 'fast start' mode.
1143         * Mainly used to influence whether such functions as mustConservePower()
1144         * assume low power mode before testing external flags.
1145         * <p>
1146         * Useful for critical customer-facing machines where start-up latency should be minimised.
1147         * <p>
1148         * Enabled by setting the system property "org.hd.d.pg2k.faststart" to true
1149         * or by being a light-weight cloud instance.
1150         * <p>
1151         * Defaults to false.
1152         * <p>
1153         * Is a method rather than a field to reduce class-initialisation
1154         * circularity issues, so is possibly a little expensive to test continuously.
1155         */
1156        public static boolean fastStartMode()
1157            {
1158            try { return(isCloudMirrorInstance() || Boolean.getBoolean("org.hd.d.pg2k.faststart")); }
1159            catch(final Exception e) { return(false); }
1160            }
1161    
1162    
1163    
1164        /**Maximum number of general properties. */
1165        public static final int MAX_GEN_PROPS = GenProps.MAX_GEN_PROPS;
1166    
1167        /**Maximum length of general property key or value (chars). */
1168        public static final int MAX_GEN_LEN = GenProps.MAX_GEN_LEN;
1169    
1170        /**Prefix for general properties in file (not needed on lookup). */
1171        public static final String GEN_PREFIX = GenPropsGenNames.GEN_PREFIX;
1172    
1173        /**The (immutable) generic properties map; never null. */
1174        private static Map<String,String> gen = Collections.emptyMap();
1175    
1176        /**Get the immutable generic properties; never null.
1177         * The keys in this Map here are stripped of the initial GEN_PREFIX
1178         * that appears in the properties file.
1179         */
1180        public static Map<String,String> getGen()
1181            {
1182            _recheck();
1183    
1184            return(gen);
1185            }
1186        }