0001: /*
0002: * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package java.util.prefs;
0027:
0028: import java.util.*;
0029: import java.io.*;
0030: import java.util.logging.Logger;
0031: import java.security.AccessController;
0032: import java.security.PrivilegedAction;
0033: import java.security.PrivilegedExceptionAction;
0034: import java.security.PrivilegedActionException;
0035:
0036: /**
0037: * Preferences implementation for Unix. Preferences are stored in the file
0038: * system, with one directory per preferences node. All of the preferences
0039: * at each node are stored in a single file. Atomic file system operations
0040: * (e.g. File.renameTo) are used to ensure integrity. An in-memory cache of
0041: * the "explored" portion of the tree is maintained for performance, and
0042: * written back to the disk periodically. File-locking is used to ensure
0043: * reasonable behavior when multiple VMs are running at the same time.
0044: * (The file lock is obtained only for sync(), flush() and removeNode().)
0045: *
0046: * @author Josh Bloch
0047: * @version 1.27, 05/05/07
0048: * @see Preferences
0049: * @since 1.4
0050: */
0051: class FileSystemPreferences extends AbstractPreferences {
0052: /**
0053: * Sync interval in seconds.
0054: */
0055: private static final int SYNC_INTERVAL = Math.max(1, Integer
0056: .parseInt((String) AccessController
0057: .doPrivileged(new PrivilegedAction() {
0058: public Object run() {
0059: return System.getProperty(
0060: "java.util.prefs.syncInterval",
0061: "30");
0062: }
0063: })));
0064:
0065: /**
0066: * Returns logger for error messages. Backing store exceptions are logged at
0067: * WARNING level.
0068: */
0069: private static Logger getLogger() {
0070: return Logger.getLogger("java.util.prefs");
0071: }
0072:
0073: /**
0074: * Directory for system preferences.
0075: */
0076: private static File systemRootDir;
0077:
0078: /*
0079: * Flag, indicating whether systemRoot directory is writable
0080: */
0081: private static boolean isSystemRootWritable;
0082:
0083: /**
0084: * Directory for user preferences.
0085: */
0086: private static File userRootDir;
0087:
0088: /*
0089: * Flag, indicating whether userRoot directory is writable
0090: */
0091: private static boolean isUserRootWritable;
0092:
0093: /**
0094: * The user root.
0095: */
0096: static Preferences userRoot = null;
0097:
0098: static synchronized Preferences getUserRoot() {
0099: if (userRoot == null) {
0100: setupUserRoot();
0101: userRoot = new FileSystemPreferences(true);
0102: }
0103: return userRoot;
0104: }
0105:
0106: private static void setupUserRoot() {
0107: AccessController.doPrivileged(new PrivilegedAction() {
0108: public Object run() {
0109: userRootDir = new File(System.getProperty(
0110: "java.util.prefs.userRoot", System
0111: .getProperty("user.home")),
0112: ".java/.userPrefs");
0113: // Attempt to create root dir if it does not yet exist.
0114: if (!userRootDir.exists()) {
0115: if (userRootDir.mkdirs()) {
0116: try {
0117: chmod(userRootDir.getCanonicalPath(),
0118: USER_RWX);
0119: } catch (IOException e) {
0120: getLogger()
0121: .warning(
0122: "Could not change permissions"
0123: + " on userRoot directory. ");
0124: }
0125: getLogger().info(
0126: "Created user preferences directory.");
0127: } else
0128: getLogger()
0129: .warning(
0130: "Couldn't create user preferences"
0131: + " directory. User preferences are unusable.");
0132: }
0133: isUserRootWritable = userRootDir.canWrite();
0134: String USER_NAME = System.getProperty("user.name");
0135: userLockFile = new File(userRootDir, ".user.lock."
0136: + USER_NAME);
0137: userRootModFile = new File(userRootDir,
0138: ".userRootModFile." + USER_NAME);
0139: if (!userRootModFile.exists())
0140: try {
0141: // create if does not exist.
0142: userRootModFile.createNewFile();
0143: // Only user can read/write userRootModFile.
0144: int result = chmod(userRootModFile
0145: .getCanonicalPath(), USER_READ_WRITE);
0146: if (result != 0)
0147: getLogger()
0148: .warning(
0149: "Problem creating userRoot "
0150: + "mod file. Chmod failed on "
0151: + userRootModFile
0152: .getCanonicalPath()
0153: + " Unix error code "
0154: + result);
0155: } catch (IOException e) {
0156: getLogger().warning(e.toString());
0157: }
0158: userRootModTime = userRootModFile.lastModified();
0159: return null;
0160: }
0161: });
0162: }
0163:
0164: /**
0165: * The system root.
0166: */
0167: static Preferences systemRoot;
0168:
0169: static synchronized Preferences getSystemRoot() {
0170: if (systemRoot == null) {
0171: setupSystemRoot();
0172: systemRoot = new FileSystemPreferences(false);
0173: }
0174: return systemRoot;
0175: }
0176:
0177: private static void setupSystemRoot() {
0178: AccessController.doPrivileged(new PrivilegedAction() {
0179: public Object run() {
0180: String systemPrefsDirName = (String) System
0181: .getProperty("java.util.prefs.systemRoot",
0182: "/etc/.java");
0183: systemRootDir = new File(systemPrefsDirName,
0184: ".systemPrefs");
0185: // Attempt to create root dir if it does not yet exist.
0186: if (!systemRootDir.exists()) {
0187: // system root does not exist in /etc/.java
0188: // Switching to java.home
0189: systemRootDir = new File(System
0190: .getProperty("java.home"), ".systemPrefs");
0191: if (!systemRootDir.exists()) {
0192: if (systemRootDir.mkdirs()) {
0193: getLogger().info(
0194: "Created system preferences directory "
0195: + "in java.home.");
0196: try {
0197: chmod(systemRootDir.getCanonicalPath(),
0198: USER_RWX_ALL_RX);
0199: } catch (IOException e) {
0200: }
0201: } else {
0202: getLogger()
0203: .warning(
0204: "Could not create "
0205: + "system preferences directory. System "
0206: + "preferences are unusable.");
0207: }
0208: }
0209: }
0210: isSystemRootWritable = systemRootDir.canWrite();
0211: systemLockFile = new File(systemRootDir, ".system.lock");
0212: systemRootModFile = new File(systemRootDir,
0213: ".systemRootModFile");
0214: if (!systemRootModFile.exists() && isSystemRootWritable)
0215: try {
0216: // create if does not exist.
0217: systemRootModFile.createNewFile();
0218: int result = chmod(systemRootModFile
0219: .getCanonicalPath(), USER_RW_ALL_READ);
0220: if (result != 0)
0221: getLogger().warning(
0222: "Chmod failed on "
0223: + systemRootModFile
0224: .getCanonicalPath()
0225: + " Unix error code "
0226: + result);
0227: } catch (IOException e) {
0228: getLogger().warning(e.toString());
0229: }
0230: systemRootModTime = systemRootModFile.lastModified();
0231: return null;
0232: }
0233: });
0234: }
0235:
0236: /**
0237: * Unix user write/read permission
0238: */
0239: private static final int USER_READ_WRITE = 0600;
0240:
0241: private static final int USER_RW_ALL_READ = 0644;
0242:
0243: private static final int USER_RWX_ALL_RX = 0755;
0244:
0245: private static final int USER_RWX = 0700;
0246:
0247: /**
0248: * The lock file for the user tree.
0249: */
0250: static File userLockFile;
0251:
0252: /**
0253: * The lock file for the system tree.
0254: */
0255: static File systemLockFile;
0256:
0257: /**
0258: * Unix lock handle for userRoot.
0259: * Zero, if unlocked.
0260: */
0261:
0262: private static int userRootLockHandle = 0;
0263:
0264: /**
0265: * Unix lock handle for systemRoot.
0266: * Zero, if unlocked.
0267: */
0268:
0269: private static int systemRootLockHandle = 0;
0270:
0271: /**
0272: * The directory representing this preference node. There is no guarantee
0273: * that this directory exits, as another VM can delete it at any time
0274: * that it (the other VM) holds the file-lock. While the root node cannot
0275: * be deleted, it may not yet have been created, or the underlying
0276: * directory could have been deleted accidentally.
0277: */
0278: private final File dir;
0279:
0280: /**
0281: * The file representing this preference node's preferences.
0282: * The file format is undocumented, and subject to change
0283: * from release to release, but I'm sure that you can figure
0284: * it out if you try real hard.
0285: */
0286: private final File prefsFile;
0287:
0288: /**
0289: * A temporary file used for saving changes to preferences. As part of
0290: * the sync operation, changes are first saved into this file, and then
0291: * atomically renamed to prefsFile. This results in an atomic state
0292: * change from one valid set of preferences to another. The
0293: * the file-lock is held for the duration of this transformation.
0294: */
0295: private final File tmpFile;
0296:
0297: /**
0298: * File, which keeps track of global modifications of userRoot.
0299: */
0300: private static File userRootModFile;
0301:
0302: /**
0303: * Flag, which indicated whether userRoot was modified by another VM
0304: */
0305: private static boolean isUserRootModified = false;
0306:
0307: /**
0308: * Keeps track of userRoot modification time. This time is reset to
0309: * zero after UNIX reboot, and is increased by 1 second each time
0310: * userRoot is modified.
0311: */
0312: private static long userRootModTime;
0313:
0314: /*
0315: * File, which keeps track of global modifications of systemRoot
0316: */
0317: private static File systemRootModFile;
0318: /*
0319: * Flag, which indicates whether systemRoot was modified by another VM
0320: */
0321: private static boolean isSystemRootModified = false;
0322:
0323: /**
0324: * Keeps track of systemRoot modification time. This time is reset to
0325: * zero after system reboot, and is increased by 1 second each time
0326: * systemRoot is modified.
0327: */
0328: private static long systemRootModTime;
0329:
0330: /**
0331: * Locally cached preferences for this node (includes uncommitted
0332: * changes). This map is initialized with from disk when the first get or
0333: * put operation occurs on this node. It is synchronized with the
0334: * corresponding disk file (prefsFile) by the sync operation. The initial
0335: * value is read *without* acquiring the file-lock.
0336: */
0337: private Map prefsCache = null;
0338:
0339: /**
0340: * The last modification time of the file backing this node at the time
0341: * that prefCache was last synchronized (or initially read). This
0342: * value is set *before* reading the file, so it's conservative; the
0343: * actual timestamp could be (slightly) higher. A value of zero indicates
0344: * that we were unable to initialize prefsCache from the disk, or
0345: * have not yet attempted to do so. (If prefsCache is non-null, it
0346: * indicates the former; if it's null, the latter.)
0347: */
0348: private long lastSyncTime = 0;
0349:
0350: /**
0351: * Unix error code for locked file.
0352: */
0353: private static final int EAGAIN = 11;
0354:
0355: /**
0356: * Unix error code for denied access.
0357: */
0358: private static final int EACCES = 13;
0359:
0360: /* Used to interpret results of native functions */
0361: private static final int LOCK_HANDLE = 0;
0362: private static final int ERROR_CODE = 1;
0363:
0364: /**
0365: * A list of all uncommitted preference changes. The elements in this
0366: * list are of type PrefChange. If this node is concurrently modified on
0367: * disk by another VM, the two sets of changes are merged when this node
0368: * is sync'ed by overwriting our prefsCache with the preference map last
0369: * written out to disk (by the other VM), and then replaying this change
0370: * log against that map. The resulting map is then written back
0371: * to the disk.
0372: */
0373: final List changeLog = new ArrayList();
0374:
0375: /**
0376: * Represents a change to a preference.
0377: */
0378: private abstract class Change {
0379: /**
0380: * Reapplies the change to prefsCache.
0381: */
0382: abstract void replay();
0383: };
0384:
0385: /**
0386: * Represents a preference put.
0387: */
0388: private class Put extends Change {
0389: String key, value;
0390:
0391: Put(String key, String value) {
0392: this .key = key;
0393: this .value = value;
0394: }
0395:
0396: void replay() {
0397: prefsCache.put(key, value);
0398: }
0399: }
0400:
0401: /**
0402: * Represents a preference remove.
0403: */
0404: private class Remove extends Change {
0405: String key;
0406:
0407: Remove(String key) {
0408: this .key = key;
0409: }
0410:
0411: void replay() {
0412: prefsCache.remove(key);
0413: }
0414: }
0415:
0416: /**
0417: * Represents the creation of this node.
0418: */
0419: private class NodeCreate extends Change {
0420: /**
0421: * Performs no action, but the presence of this object in changeLog
0422: * will force the node and its ancestors to be made permanent at the
0423: * next sync.
0424: */
0425: void replay() {
0426: }
0427: }
0428:
0429: /**
0430: * NodeCreate object for this node.
0431: */
0432: NodeCreate nodeCreate = null;
0433:
0434: /**
0435: * Replay changeLog against prefsCache.
0436: */
0437: private void replayChanges() {
0438: for (int i = 0, n = changeLog.size(); i < n; i++)
0439: ((Change) changeLog.get(i)).replay();
0440: }
0441:
0442: private static Timer syncTimer = new Timer(true); // Daemon Thread
0443:
0444: static {
0445: // Add periodic timer task to periodically sync cached prefs
0446: syncTimer.schedule(new TimerTask() {
0447: public void run() {
0448: syncWorld();
0449: }
0450: }, SYNC_INTERVAL * 1000, SYNC_INTERVAL * 1000);
0451:
0452: // Add shutdown hook to flush cached prefs on normal termination
0453: AccessController.doPrivileged(new PrivilegedAction() {
0454: public Object run() {
0455: Runtime.getRuntime().addShutdownHook(new Thread() {
0456: public void run() {
0457: syncTimer.cancel();
0458: syncWorld();
0459: }
0460: });
0461: return null;
0462: }
0463: });
0464: }
0465:
0466: private static void syncWorld() {
0467: /*
0468: * Synchronization necessary because userRoot and systemRoot are
0469: * lazily initialized.
0470: */
0471: Preferences userRt;
0472: Preferences systemRt;
0473: synchronized (FileSystemPreferences.class) {
0474: userRt = userRoot;
0475: systemRt = systemRoot;
0476: }
0477:
0478: try {
0479: if (userRt != null)
0480: userRt.flush();
0481: } catch (BackingStoreException e) {
0482: getLogger().warning("Couldn't flush user prefs: " + e);
0483: }
0484:
0485: try {
0486: if (systemRt != null)
0487: systemRt.flush();
0488: } catch (BackingStoreException e) {
0489: getLogger().warning("Couldn't flush system prefs: " + e);
0490: }
0491: }
0492:
0493: private final boolean isUserNode;
0494:
0495: /**
0496: * Special constructor for roots (both user and system). This constructor
0497: * will only be called twice, by the static initializer.
0498: */
0499: private FileSystemPreferences(boolean user) {
0500: super (null, "");
0501: isUserNode = user;
0502: dir = (user ? userRootDir : systemRootDir);
0503: prefsFile = new File(dir, "prefs.xml");
0504: tmpFile = new File(dir, "prefs.tmp");
0505: }
0506:
0507: /**
0508: * Construct a new FileSystemPreferences instance with the specified
0509: * parent node and name. This constructor, called from childSpi,
0510: * is used to make every node except for the two //roots.
0511: */
0512: private FileSystemPreferences(FileSystemPreferences parent,
0513: String name) {
0514: super (parent, name);
0515: isUserNode = parent.isUserNode;
0516: dir = new File(parent.dir, dirName(name));
0517: prefsFile = new File(dir, "prefs.xml");
0518: tmpFile = new File(dir, "prefs.tmp");
0519: AccessController.doPrivileged(new PrivilegedAction() {
0520: public Object run() {
0521: newNode = !dir.exists();
0522: return null;
0523: }
0524: });
0525: if (newNode) {
0526: // These 2 things guarantee node will get wrtten at next flush/sync
0527: prefsCache = new TreeMap();
0528: nodeCreate = new NodeCreate();
0529: changeLog.add(nodeCreate);
0530: }
0531: }
0532:
0533: public boolean isUserNode() {
0534: return isUserNode;
0535: }
0536:
0537: protected void putSpi(String key, String value) {
0538: initCacheIfNecessary();
0539: changeLog.add(new Put(key, value));
0540: prefsCache.put(key, value);
0541: }
0542:
0543: protected String getSpi(String key) {
0544: initCacheIfNecessary();
0545: return (String) prefsCache.get(key);
0546: }
0547:
0548: protected void removeSpi(String key) {
0549: initCacheIfNecessary();
0550: changeLog.add(new Remove(key));
0551: prefsCache.remove(key);
0552: }
0553:
0554: /**
0555: * Initialize prefsCache if it has yet to be initialized. When this method
0556: * returns, prefsCache will be non-null. If the data was successfully
0557: * read from the file, lastSyncTime will be updated. If prefsCache was
0558: * null, but it was impossible to read the file (because it didn't
0559: * exist or for any other reason) prefsCache will be initialized to an
0560: * empty, modifiable Map, and lastSyncTime remain zero.
0561: */
0562: private void initCacheIfNecessary() {
0563: if (prefsCache != null)
0564: return;
0565:
0566: try {
0567: loadCache();
0568: } catch (Exception e) {
0569: // assert lastSyncTime == 0;
0570: prefsCache = new TreeMap();
0571: }
0572: }
0573:
0574: /**
0575: * Attempt to load prefsCache from the backing store. If the attempt
0576: * succeeds, lastSyncTime will be updated (the new value will typically
0577: * correspond to the data loaded into the map, but it may be less,
0578: * if another VM is updating this node concurrently). If the attempt
0579: * fails, a BackingStoreException is thrown and both prefsCache and
0580: * lastSyncTime are unaffected by the call.
0581: */
0582: private void loadCache() throws BackingStoreException {
0583: try {
0584: AccessController
0585: .doPrivileged(new PrivilegedExceptionAction() {
0586: public Object run()
0587: throws BackingStoreException {
0588: Map m = new TreeMap();
0589: long newLastSyncTime = 0;
0590: try {
0591: newLastSyncTime = prefsFile
0592: .lastModified();
0593: FileInputStream fis = new FileInputStream(
0594: prefsFile);
0595: XmlSupport.importMap(fis, m);
0596: fis.close();
0597: } catch (Exception e) {
0598: if (e instanceof InvalidPreferencesFormatException) {
0599: getLogger().warning(
0600: "Invalid preferences format in "
0601: + prefsFile
0602: .getPath());
0603: prefsFile
0604: .renameTo(new File(
0605: prefsFile
0606: .getParentFile(),
0607: "IncorrectFormatPrefs.xml"));
0608: m = new TreeMap();
0609: } else if (e instanceof FileNotFoundException) {
0610: getLogger().warning(
0611: "Prefs file removed in background "
0612: + prefsFile
0613: .getPath());
0614: } else {
0615: throw new BackingStoreException(e);
0616: }
0617: }
0618: // Attempt succeeded; update state
0619: prefsCache = m;
0620: lastSyncTime = newLastSyncTime;
0621: return null;
0622: }
0623: });
0624: } catch (PrivilegedActionException e) {
0625: throw (BackingStoreException) e.getException();
0626: }
0627: }
0628:
0629: /**
0630: * Attempt to write back prefsCache to the backing store. If the attempt
0631: * succeeds, lastSyncTime will be updated (the new value will correspond
0632: * exactly to the data thust written back, as we hold the file lock, which
0633: * prevents a concurrent write. If the attempt fails, a
0634: * BackingStoreException is thrown and both the backing store (prefsFile)
0635: * and lastSyncTime will be unaffected by this call. This call will
0636: * NEVER leave prefsFile in a corrupt state.
0637: */
0638: private void writeBackCache() throws BackingStoreException {
0639: try {
0640: AccessController
0641: .doPrivileged(new PrivilegedExceptionAction() {
0642: public Object run()
0643: throws BackingStoreException {
0644: try {
0645: if (!dir.exists() && !dir.mkdirs())
0646: throw new BackingStoreException(dir
0647: + " create failed.");
0648: FileOutputStream fos = new FileOutputStream(
0649: tmpFile);
0650: XmlSupport.exportMap(fos, prefsCache);
0651: fos.close();
0652: if (!tmpFile.renameTo(prefsFile))
0653: throw new BackingStoreException(
0654: "Can't rename " + tmpFile
0655: + " to "
0656: + prefsFile);
0657: } catch (Exception e) {
0658: if (e instanceof BackingStoreException)
0659: throw (BackingStoreException) e;
0660: throw new BackingStoreException(e);
0661: }
0662: return null;
0663: }
0664: });
0665: } catch (PrivilegedActionException e) {
0666: throw (BackingStoreException) e.getException();
0667: }
0668: }
0669:
0670: protected String[] keysSpi() {
0671: initCacheIfNecessary();
0672: return (String[]) prefsCache.keySet().toArray(
0673: new String[prefsCache.size()]);
0674: }
0675:
0676: protected String[] childrenNamesSpi() {
0677: return (String[]) AccessController
0678: .doPrivileged(new PrivilegedAction() {
0679: public Object run() {
0680: List result = new ArrayList();
0681: File[] dirContents = dir.listFiles();
0682: if (dirContents != null) {
0683: for (int i = 0; i < dirContents.length; i++)
0684: if (dirContents[i].isDirectory())
0685: result.add(nodeName(dirContents[i]
0686: .getName()));
0687: }
0688: return result.toArray(EMPTY_STRING_ARRAY);
0689: }
0690: });
0691: }
0692:
0693: private static final String[] EMPTY_STRING_ARRAY = new String[0];
0694:
0695: protected AbstractPreferences childSpi(String name) {
0696: return new FileSystemPreferences(this , name);
0697: }
0698:
0699: public void removeNode() throws BackingStoreException {
0700: synchronized (isUserNode() ? userLockFile : systemLockFile) {
0701: // to remove a node we need an exclusive lock
0702: if (!lockFile(false))
0703: throw (new BackingStoreException(
0704: "Couldn't get file lock."));
0705: try {
0706: super .removeNode();
0707: } finally {
0708: unlockFile();
0709: }
0710: }
0711: }
0712:
0713: /**
0714: * Called with file lock held (in addition to node locks).
0715: */
0716: protected void removeNodeSpi() throws BackingStoreException {
0717: try {
0718: AccessController
0719: .doPrivileged(new PrivilegedExceptionAction() {
0720: public Object run()
0721: throws BackingStoreException {
0722: if (changeLog.contains(nodeCreate)) {
0723: changeLog.remove(nodeCreate);
0724: nodeCreate = null;
0725: return null;
0726: }
0727: if (!dir.exists())
0728: return null;
0729: prefsFile.delete();
0730: tmpFile.delete();
0731: // dir should be empty now. If it's not, empty it
0732: File[] junk = dir.listFiles();
0733: if (junk.length != 0) {
0734: getLogger().warning(
0735: "Found extraneous files when removing node: "
0736: + Arrays.asList(junk));
0737: for (int i = 0; i < junk.length; i++)
0738: junk[i].delete();
0739: }
0740: if (!dir.delete())
0741: throw new BackingStoreException(
0742: "Couldn't delete dir: " + dir);
0743: return null;
0744: }
0745: });
0746: } catch (PrivilegedActionException e) {
0747: throw (BackingStoreException) e.getException();
0748: }
0749: }
0750:
0751: public synchronized void sync() throws BackingStoreException {
0752: boolean userNode = isUserNode();
0753: boolean shared;
0754:
0755: if (userNode) {
0756: shared = false; /* use exclusive lock for user prefs */
0757: } else {
0758: /* if can write to system root, use exclusive lock.
0759: otherwise use shared lock. */
0760: shared = !isSystemRootWritable;
0761: }
0762: synchronized (isUserNode() ? userLockFile : systemLockFile) {
0763: if (!lockFile(shared))
0764: throw (new BackingStoreException(
0765: "Couldn't get file lock."));
0766: final Long newModTime = (Long) AccessController
0767: .doPrivileged(new PrivilegedAction() {
0768: public Object run() {
0769: long nmt;
0770: if (isUserNode()) {
0771: nmt = userRootModFile.lastModified();
0772: isUserRootModified = userRootModTime == nmt;
0773: } else {
0774: nmt = systemRootModFile.lastModified();
0775: isSystemRootModified = systemRootModTime == nmt;
0776: }
0777: return new Long(nmt);
0778: }
0779: });
0780: try {
0781: super .sync();
0782: AccessController.doPrivileged(new PrivilegedAction() {
0783: public Object run() {
0784: if (isUserNode()) {
0785: userRootModTime = newModTime.longValue() + 1000;
0786: userRootModFile
0787: .setLastModified(userRootModTime);
0788: } else {
0789: systemRootModTime = newModTime.longValue() + 1000;
0790: systemRootModFile
0791: .setLastModified(systemRootModTime);
0792: }
0793: return null;
0794: }
0795: });
0796: } finally {
0797: unlockFile();
0798: }
0799: }
0800: }
0801:
0802: protected void syncSpi() throws BackingStoreException {
0803: try {
0804: AccessController
0805: .doPrivileged(new PrivilegedExceptionAction() {
0806: public Object run()
0807: throws BackingStoreException {
0808: syncSpiPrivileged();
0809: return null;
0810: }
0811: });
0812: } catch (PrivilegedActionException e) {
0813: throw (BackingStoreException) e.getException();
0814: }
0815: }
0816:
0817: private void syncSpiPrivileged() throws BackingStoreException {
0818: if (isRemoved())
0819: throw new IllegalStateException("Node has been removed");
0820: if (prefsCache == null)
0821: return; // We've never been used, don't bother syncing
0822: long lastModifiedTime;
0823: if ((isUserNode() ? isUserRootModified : isSystemRootModified)) {
0824: lastModifiedTime = prefsFile.lastModified();
0825: if (lastModifiedTime != lastSyncTime) {
0826: // Prefs at this node were externally modified; read in node and
0827: // playback any local mods since last sync
0828: loadCache();
0829: replayChanges();
0830: lastSyncTime = lastModifiedTime;
0831: }
0832: } else if (lastSyncTime != 0 && !dir.exists()) {
0833: // This node was removed in the background. Playback any changes
0834: // against a virgin (empty) Map.
0835: prefsCache = new TreeMap();
0836: replayChanges();
0837: }
0838: if (!changeLog.isEmpty()) {
0839: writeBackCache(); // Creates directory & file if necessary
0840: /*
0841: * Attempt succeeded; it's barely possible that the call to
0842: * lastModified might fail (i.e., return 0), but this would not
0843: * be a disaster, as lastSyncTime is allowed to lag.
0844: */
0845: lastModifiedTime = prefsFile.lastModified();
0846: /* If lastSyncTime did not change, or went back
0847: * increment by 1 second. Since we hold the lock
0848: * lastSyncTime always monotonically encreases in the
0849: * atomic sense.
0850: */
0851: if (lastSyncTime <= lastModifiedTime) {
0852: lastSyncTime = lastModifiedTime + 1000;
0853: prefsFile.setLastModified(lastSyncTime);
0854: }
0855: changeLog.clear();
0856: }
0857: }
0858:
0859: public void flush() throws BackingStoreException {
0860: if (isRemoved())
0861: return;
0862: sync();
0863: }
0864:
0865: protected void flushSpi() throws BackingStoreException {
0866: // assert false;
0867: }
0868:
0869: /**
0870: * Returns true if the specified character is appropriate for use in
0871: * Unix directory names. A character is appropriate if it's a printable
0872: * ASCII character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f),
0873: * dot ('.', 0x2e), or underscore ('_', 0x5f).
0874: */
0875: private static boolean isDirChar(char ch) {
0876: return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.'
0877: && ch != '_';
0878: }
0879:
0880: /**
0881: * Returns the directory name corresponding to the specified node name.
0882: * Generally, this is just the node name. If the node name includes
0883: * inappropriate characters (as per isDirChar) it is translated to Base64.
0884: * with the underscore character ('_', 0x5f) prepended.
0885: */
0886: private static String dirName(String nodeName) {
0887: for (int i = 0, n = nodeName.length(); i < n; i++)
0888: if (!isDirChar(nodeName.charAt(i)))
0889: return "_"
0890: + Base64
0891: .byteArrayToAltBase64(byteArray(nodeName));
0892: return nodeName;
0893: }
0894:
0895: /**
0896: * Translate a string into a byte array by translating each character
0897: * into two bytes, high-byte first ("big-endian").
0898: */
0899: private static byte[] byteArray(String s) {
0900: int len = s.length();
0901: byte[] result = new byte[2 * len];
0902: for (int i = 0, j = 0; i < len; i++) {
0903: char c = s.charAt(i);
0904: result[j++] = (byte) (c >> 8);
0905: result[j++] = (byte) c;
0906: }
0907: return result;
0908: }
0909:
0910: /**
0911: * Returns the node name corresponding to the specified directory name.
0912: * (Inverts the transformation of dirName(String).
0913: */
0914: private static String nodeName(String dirName) {
0915: if (dirName.charAt(0) != '_')
0916: return dirName;
0917: byte a[] = Base64.altBase64ToByteArray(dirName.substring(1));
0918: StringBuffer result = new StringBuffer(a.length / 2);
0919: for (int i = 0; i < a.length;) {
0920: int highByte = a[i++] & 0xff;
0921: int lowByte = a[i++] & 0xff;
0922: result.append((char) ((highByte << 8) | lowByte));
0923: }
0924: return result.toString();
0925: }
0926:
0927: /**
0928: * Try to acquire the appropriate file lock (user or system). If
0929: * the initial attempt fails, several more attempts are made using
0930: * an exponential backoff strategy. If all attempts fail, this method
0931: * returns false.
0932: * @throws SecurityException if file access denied.
0933: */
0934: private boolean lockFile(boolean shared) throws SecurityException {
0935: boolean usernode = isUserNode();
0936: int[] result;
0937: int errorCode = 0;
0938: File lockFile = (usernode ? userLockFile : systemLockFile);
0939: long sleepTime = INIT_SLEEP_TIME;
0940: for (int i = 0; i < MAX_ATTEMPTS; i++) {
0941: try {
0942: int perm = (usernode ? USER_READ_WRITE
0943: : USER_RW_ALL_READ);
0944: result = lockFile0(lockFile.getCanonicalPath(), perm,
0945: shared);
0946:
0947: errorCode = result[ERROR_CODE];
0948: if (result[LOCK_HANDLE] != 0) {
0949: if (usernode) {
0950: userRootLockHandle = result[LOCK_HANDLE];
0951: } else {
0952: systemRootLockHandle = result[LOCK_HANDLE];
0953: }
0954: return true;
0955: }
0956: } catch (IOException e) {
0957: // // If at first, you don't succeed...
0958: }
0959:
0960: try {
0961: Thread.sleep(sleepTime);
0962: } catch (InterruptedException e) {
0963: checkLockFile0ErrorCode(errorCode);
0964: return false;
0965: }
0966: sleepTime *= 2;
0967: }
0968: checkLockFile0ErrorCode(errorCode);
0969: return false;
0970: }
0971:
0972: /**
0973: * Checks if unlockFile0() returned an error. Throws a SecurityException,
0974: * if access denied. Logs a warning otherwise.
0975: */
0976: private void checkLockFile0ErrorCode(int errorCode)
0977: throws SecurityException {
0978: if (errorCode == EACCES)
0979: throw new SecurityException("Could not lock "
0980: + (isUserNode() ? "User prefs." : "System prefs.")
0981: + " Lock file access denied.");
0982: if (errorCode != EAGAIN)
0983: getLogger().warning(
0984: "Could not lock "
0985: + (isUserNode() ? "User prefs. "
0986: : "System prefs.")
0987: + " Unix error code " + errorCode + ".");
0988: }
0989:
0990: /**
0991: * Locks file using UNIX file locking.
0992: * @param fileName Absolute file name of the lock file.
0993: * @return Returns a lock handle, used to unlock the file.
0994: */
0995: private static native int[] lockFile0(String fileName,
0996: int permission, boolean shared);
0997:
0998: /**
0999: * Unlocks file previously locked by lockFile0().
1000: * @param lockHandle Handle to the file lock.
1001: * @return Returns zero if OK, UNIX error code if failure.
1002: */
1003: private static native int unlockFile0(int lockHandle);
1004:
1005: /**
1006: * Changes UNIX file permissions.
1007: */
1008: private static native int chmod(String fileName, int permission);
1009:
1010: /**
1011: * Initial time between lock attempts, in ms. The time is doubled
1012: * after each failing attempt (except the first).
1013: */
1014: private static int INIT_SLEEP_TIME = 50;
1015:
1016: /**
1017: * Maximum number of lock attempts.
1018: */
1019: private static int MAX_ATTEMPTS = 5;
1020:
1021: /**
1022: * Release the the appropriate file lock (user or system).
1023: * @throws SecurityException if file access denied.
1024: */
1025: private void unlockFile() {
1026: int result;
1027: boolean usernode = isUserNode();
1028: File lockFile = (usernode ? userLockFile : systemLockFile);
1029: int lockHandle = (usernode ? userRootLockHandle
1030: : systemRootLockHandle);
1031: if (lockHandle == 0) {
1032: getLogger().warning(
1033: "Unlock: zero lockHandle for "
1034: + (usernode ? "user" : "system")
1035: + " preferences.)");
1036: return;
1037: }
1038: result = unlockFile0(lockHandle);
1039: if (result != 0) {
1040: getLogger().warning(
1041: "Could not drop file-lock on "
1042: + (isUserNode() ? "user" : "system")
1043: + " preferences." + " Unix error code "
1044: + result + ".");
1045: if (result == EACCES)
1046: throw new SecurityException("Could not unlock"
1047: + (isUserNode() ? "User prefs."
1048: : "System prefs.")
1049: + " Lock file access denied.");
1050: }
1051: if (isUserNode()) {
1052: userRootLockHandle = 0;
1053: } else {
1054: systemRootLockHandle = 0;
1055: }
1056: }
1057: }
|