0N/A/*
3909N/A * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage java.util.prefs;
0N/Aimport java.util.*;
0N/Aimport java.io.*;
0N/Aimport java.security.AccessController;
0N/Aimport java.security.PrivilegedAction;
0N/Aimport java.security.PrivilegedExceptionAction;
0N/Aimport java.security.PrivilegedActionException;
0N/A
1864N/Aimport sun.util.logging.PlatformLogger;
0N/A
0N/A/**
0N/A * Preferences implementation for Unix. Preferences are stored in the file
0N/A * system, with one directory per preferences node. All of the preferences
0N/A * at each node are stored in a single file. Atomic file system operations
0N/A * (e.g. File.renameTo) are used to ensure integrity. An in-memory cache of
0N/A * the "explored" portion of the tree is maintained for performance, and
0N/A * written back to the disk periodically. File-locking is used to ensure
0N/A * reasonable behavior when multiple VMs are running at the same time.
0N/A * (The file lock is obtained only for sync(), flush() and removeNode().)
0N/A *
0N/A * @author Josh Bloch
0N/A * @see Preferences
0N/A * @since 1.4
0N/A */
0N/Aclass FileSystemPreferences extends AbstractPreferences {
0N/A /**
0N/A * Sync interval in seconds.
0N/A */
0N/A private static final int SYNC_INTERVAL = Math.max(1,
28N/A Integer.parseInt(
28N/A AccessController.doPrivileged(
28N/A new sun.security.action.GetPropertyAction(
28N/A "java.util.prefs.syncInterval", "30"))));
0N/A
0N/A /**
0N/A * Returns logger for error messages. Backing store exceptions are logged at
0N/A * WARNING level.
0N/A */
1864N/A private static PlatformLogger getLogger() {
1864N/A return PlatformLogger.getLogger("java.util.prefs");
0N/A }
0N/A
0N/A /**
0N/A * Directory for system preferences.
0N/A */
0N/A private static File systemRootDir;
0N/A
0N/A /*
0N/A * Flag, indicating whether systemRoot directory is writable
0N/A */
0N/A private static boolean isSystemRootWritable;
0N/A
0N/A /**
0N/A * Directory for user preferences.
0N/A */
0N/A private static File userRootDir;
0N/A
0N/A /*
0N/A * Flag, indicating whether userRoot directory is writable
0N/A */
0N/A private static boolean isUserRootWritable;
0N/A
0N/A /**
0N/A * The user root.
0N/A */
0N/A static Preferences userRoot = null;
0N/A
0N/A static synchronized Preferences getUserRoot() {
0N/A if (userRoot == null) {
0N/A setupUserRoot();
0N/A userRoot = new FileSystemPreferences(true);
0N/A }
0N/A return userRoot;
0N/A }
0N/A
0N/A private static void setupUserRoot() {
28N/A AccessController.doPrivileged(new PrivilegedAction<Void>() {
28N/A public Void run() {
0N/A userRootDir =
0N/A new File(System.getProperty("java.util.prefs.userRoot",
0N/A System.getProperty("user.home")), ".java/.userPrefs");
0N/A // Attempt to create root dir if it does not yet exist.
0N/A if (!userRootDir.exists()) {
0N/A if (userRootDir.mkdirs()) {
0N/A try {
0N/A chmod(userRootDir.getCanonicalPath(), USER_RWX);
0N/A } catch (IOException e) {
0N/A getLogger().warning("Could not change permissions" +
0N/A " on userRoot directory. ");
0N/A }
0N/A getLogger().info("Created user preferences directory.");
0N/A }
0N/A else
0N/A getLogger().warning("Couldn't create user preferences" +
0N/A " directory. User preferences are unusable.");
0N/A }
0N/A isUserRootWritable = userRootDir.canWrite();
0N/A String USER_NAME = System.getProperty("user.name");
0N/A userLockFile = new File (userRootDir,".user.lock." + USER_NAME);
0N/A userRootModFile = new File (userRootDir,
0N/A ".userRootModFile." + USER_NAME);
0N/A if (!userRootModFile.exists())
0N/A try {
0N/A // create if does not exist.
0N/A userRootModFile.createNewFile();
0N/A // Only user can read/write userRootModFile.
0N/A int result = chmod(userRootModFile.getCanonicalPath(),
0N/A USER_READ_WRITE);
0N/A if (result !=0)
0N/A getLogger().warning("Problem creating userRoot " +
0N/A "mod file. Chmod failed on " +
0N/A userRootModFile.getCanonicalPath() +
0N/A " Unix error code " + result);
0N/A } catch (IOException e) {
0N/A getLogger().warning(e.toString());
0N/A }
0N/A userRootModTime = userRootModFile.lastModified();
0N/A return null;
0N/A }
0N/A });
0N/A }
0N/A
0N/A
0N/A /**
0N/A * The system root.
0N/A */
0N/A static Preferences systemRoot;
0N/A
0N/A static synchronized Preferences getSystemRoot() {
0N/A if (systemRoot == null) {
0N/A setupSystemRoot();
0N/A systemRoot = new FileSystemPreferences(false);
0N/A }
0N/A return systemRoot;
0N/A }
0N/A
0N/A private static void setupSystemRoot() {
28N/A AccessController.doPrivileged(new PrivilegedAction<Void>() {
28N/A public Void run() {
28N/A String systemPrefsDirName =
0N/A System.getProperty("java.util.prefs.systemRoot","/etc/.java");
0N/A systemRootDir =
0N/A new File(systemPrefsDirName, ".systemPrefs");
0N/A // Attempt to create root dir if it does not yet exist.
0N/A if (!systemRootDir.exists()) {
0N/A // system root does not exist in /etc/.java
0N/A // Switching to java.home
0N/A systemRootDir =
0N/A new File(System.getProperty("java.home"),
0N/A ".systemPrefs");
0N/A if (!systemRootDir.exists()) {
0N/A if (systemRootDir.mkdirs()) {
0N/A getLogger().info(
0N/A "Created system preferences directory "
0N/A + "in java.home.");
0N/A try {
0N/A chmod(systemRootDir.getCanonicalPath(),
0N/A USER_RWX_ALL_RX);
0N/A } catch (IOException e) {
0N/A }
0N/A } else {
0N/A getLogger().warning("Could not create "
0N/A + "system preferences directory. System "
0N/A + "preferences are unusable.");
0N/A }
0N/A }
0N/A }
0N/A isSystemRootWritable = systemRootDir.canWrite();
0N/A systemLockFile = new File(systemRootDir, ".system.lock");
0N/A systemRootModFile =
0N/A new File (systemRootDir,".systemRootModFile");
0N/A if (!systemRootModFile.exists() && isSystemRootWritable)
0N/A try {
0N/A // create if does not exist.
0N/A systemRootModFile.createNewFile();
0N/A int result = chmod(systemRootModFile.getCanonicalPath(),
0N/A USER_RW_ALL_READ);
0N/A if (result !=0)
0N/A getLogger().warning("Chmod failed on " +
0N/A systemRootModFile.getCanonicalPath() +
0N/A " Unix error code " + result);
0N/A } catch (IOException e) { getLogger().warning(e.toString());
0N/A }
0N/A systemRootModTime = systemRootModFile.lastModified();
0N/A return null;
0N/A }
0N/A });
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Unix user write/read permission
0N/A */
0N/A private static final int USER_READ_WRITE = 0600;
0N/A
0N/A private static final int USER_RW_ALL_READ = 0644;
0N/A
0N/A
0N/A private static final int USER_RWX_ALL_RX = 0755;
0N/A
0N/A private static final int USER_RWX = 0700;
0N/A
0N/A /**
0N/A * The lock file for the user tree.
0N/A */
0N/A static File userLockFile;
0N/A
0N/A
0N/A
0N/A /**
0N/A * The lock file for the system tree.
0N/A */
0N/A static File systemLockFile;
0N/A
0N/A /**
0N/A * Unix lock handle for userRoot.
0N/A * Zero, if unlocked.
0N/A */
0N/A
0N/A private static int userRootLockHandle = 0;
0N/A
0N/A /**
0N/A * Unix lock handle for systemRoot.
0N/A * Zero, if unlocked.
0N/A */
0N/A
0N/A private static int systemRootLockHandle = 0;
0N/A
0N/A /**
0N/A * The directory representing this preference node. There is no guarantee
0N/A * that this directory exits, as another VM can delete it at any time
0N/A * that it (the other VM) holds the file-lock. While the root node cannot
0N/A * be deleted, it may not yet have been created, or the underlying
0N/A * directory could have been deleted accidentally.
0N/A */
0N/A private final File dir;
0N/A
0N/A /**
0N/A * The file representing this preference node's preferences.
0N/A * The file format is undocumented, and subject to change
0N/A * from release to release, but I'm sure that you can figure
0N/A * it out if you try real hard.
0N/A */
0N/A private final File prefsFile;
0N/A
0N/A /**
0N/A * A temporary file used for saving changes to preferences. As part of
0N/A * the sync operation, changes are first saved into this file, and then
0N/A * atomically renamed to prefsFile. This results in an atomic state
0N/A * change from one valid set of preferences to another. The
0N/A * the file-lock is held for the duration of this transformation.
0N/A */
0N/A private final File tmpFile;
0N/A
0N/A /**
0N/A * File, which keeps track of global modifications of userRoot.
0N/A */
0N/A private static File userRootModFile;
0N/A
0N/A /**
0N/A * Flag, which indicated whether userRoot was modified by another VM
0N/A */
0N/A private static boolean isUserRootModified = false;
0N/A
0N/A /**
0N/A * Keeps track of userRoot modification time. This time is reset to
0N/A * zero after UNIX reboot, and is increased by 1 second each time
0N/A * userRoot is modified.
0N/A */
0N/A private static long userRootModTime;
0N/A
0N/A
0N/A /*
0N/A * File, which keeps track of global modifications of systemRoot
0N/A */
0N/A private static File systemRootModFile;
0N/A /*
0N/A * Flag, which indicates whether systemRoot was modified by another VM
0N/A */
0N/A private static boolean isSystemRootModified = false;
0N/A
0N/A /**
0N/A * Keeps track of systemRoot modification time. This time is reset to
0N/A * zero after system reboot, and is increased by 1 second each time
0N/A * systemRoot is modified.
0N/A */
0N/A private static long systemRootModTime;
0N/A
0N/A /**
0N/A * Locally cached preferences for this node (includes uncommitted
0N/A * changes). This map is initialized with from disk when the first get or
0N/A * put operation occurs on this node. It is synchronized with the
0N/A * corresponding disk file (prefsFile) by the sync operation. The initial
0N/A * value is read *without* acquiring the file-lock.
0N/A */
28N/A private Map<String, String> prefsCache = null;
0N/A
0N/A /**
0N/A * The last modification time of the file backing this node at the time
0N/A * that prefCache was last synchronized (or initially read). This
0N/A * value is set *before* reading the file, so it's conservative; the
0N/A * actual timestamp could be (slightly) higher. A value of zero indicates
0N/A * that we were unable to initialize prefsCache from the disk, or
0N/A * have not yet attempted to do so. (If prefsCache is non-null, it
0N/A * indicates the former; if it's null, the latter.)
0N/A */
0N/A private long lastSyncTime = 0;
0N/A
0N/A /**
0N/A * Unix error code for locked file.
0N/A */
0N/A private static final int EAGAIN = 11;
0N/A
0N/A /**
0N/A * Unix error code for denied access.
0N/A */
0N/A private static final int EACCES = 13;
0N/A
0N/A /* Used to interpret results of native functions */
0N/A private static final int LOCK_HANDLE = 0;
0N/A private static final int ERROR_CODE = 1;
0N/A
0N/A /**
0N/A * A list of all uncommitted preference changes. The elements in this
0N/A * list are of type PrefChange. If this node is concurrently modified on
0N/A * disk by another VM, the two sets of changes are merged when this node
0N/A * is sync'ed by overwriting our prefsCache with the preference map last
0N/A * written out to disk (by the other VM), and then replaying this change
0N/A * log against that map. The resulting map is then written back
0N/A * to the disk.
0N/A */
3323N/A final List<Change> changeLog = new ArrayList<>();
0N/A
0N/A /**
0N/A * Represents a change to a preference.
0N/A */
0N/A private abstract class Change {
0N/A /**
0N/A * Reapplies the change to prefsCache.
0N/A */
0N/A abstract void replay();
0N/A };
0N/A
0N/A /**
0N/A * Represents a preference put.
0N/A */
0N/A private class Put extends Change {
0N/A String key, value;
0N/A
0N/A Put(String key, String value) {
0N/A this.key = key;
0N/A this.value = value;
0N/A }
0N/A
0N/A void replay() {
0N/A prefsCache.put(key, value);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Represents a preference remove.
0N/A */
0N/A private class Remove extends Change {
0N/A String key;
0N/A
0N/A Remove(String key) {
0N/A this.key = key;
0N/A }
0N/A
0N/A void replay() {
0N/A prefsCache.remove(key);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Represents the creation of this node.
0N/A */
0N/A private class NodeCreate extends Change {
0N/A /**
0N/A * Performs no action, but the presence of this object in changeLog
0N/A * will force the node and its ancestors to be made permanent at the
0N/A * next sync.
0N/A */
0N/A void replay() {
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * NodeCreate object for this node.
0N/A */
0N/A NodeCreate nodeCreate = null;
0N/A
0N/A /**
0N/A * Replay changeLog against prefsCache.
0N/A */
0N/A private void replayChanges() {
0N/A for (int i = 0, n = changeLog.size(); i<n; i++)
28N/A changeLog.get(i).replay();
0N/A }
0N/A
0N/A private static Timer syncTimer = new Timer(true); // Daemon Thread
0N/A
0N/A static {
0N/A // Add periodic timer task to periodically sync cached prefs
0N/A syncTimer.schedule(new TimerTask() {
0N/A public void run() {
0N/A syncWorld();
0N/A }
0N/A }, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000);
0N/A
0N/A // Add shutdown hook to flush cached prefs on normal termination
28N/A AccessController.doPrivileged(new PrivilegedAction<Void>() {
28N/A public Void run() {
0N/A Runtime.getRuntime().addShutdownHook(new Thread() {
0N/A public void run() {
0N/A syncTimer.cancel();
0N/A syncWorld();
0N/A }
0N/A });
0N/A return null;
0N/A }
0N/A });
0N/A }
0N/A
0N/A private static void syncWorld() {
0N/A /*
0N/A * Synchronization necessary because userRoot and systemRoot are
0N/A * lazily initialized.
0N/A */
0N/A Preferences userRt;
0N/A Preferences systemRt;
0N/A synchronized(FileSystemPreferences.class) {
0N/A userRt = userRoot;
0N/A systemRt = systemRoot;
0N/A }
0N/A
0N/A try {
0N/A if (userRt != null)
0N/A userRt.flush();
0N/A } catch(BackingStoreException e) {
0N/A getLogger().warning("Couldn't flush user prefs: " + e);
0N/A }
0N/A
0N/A try {
0N/A if (systemRt != null)
0N/A systemRt.flush();
0N/A } catch(BackingStoreException e) {
0N/A getLogger().warning("Couldn't flush system prefs: " + e);
0N/A }
0N/A }
0N/A
0N/A private final boolean isUserNode;
0N/A
0N/A /**
0N/A * Special constructor for roots (both user and system). This constructor
0N/A * will only be called twice, by the static initializer.
0N/A */
0N/A private FileSystemPreferences(boolean user) {
0N/A super(null, "");
0N/A isUserNode = user;
0N/A dir = (user ? userRootDir: systemRootDir);
0N/A prefsFile = new File(dir, "prefs.xml");
0N/A tmpFile = new File(dir, "prefs.tmp");
0N/A }
0N/A
0N/A /**
0N/A * Construct a new FileSystemPreferences instance with the specified
0N/A * parent node and name. This constructor, called from childSpi,
0N/A * is used to make every node except for the two //roots.
0N/A */
0N/A private FileSystemPreferences(FileSystemPreferences parent, String name) {
0N/A super(parent, name);
0N/A isUserNode = parent.isUserNode;
0N/A dir = new File(parent.dir, dirName(name));
0N/A prefsFile = new File(dir, "prefs.xml");
0N/A tmpFile = new File(dir, "prefs.tmp");
28N/A AccessController.doPrivileged(new PrivilegedAction<Void>() {
28N/A public Void run() {
0N/A newNode = !dir.exists();
0N/A return null;
0N/A }
0N/A });
0N/A if (newNode) {
0N/A // These 2 things guarantee node will get wrtten at next flush/sync
3323N/A prefsCache = new TreeMap<>();
0N/A nodeCreate = new NodeCreate();
0N/A changeLog.add(nodeCreate);
0N/A }
0N/A }
0N/A
0N/A public boolean isUserNode() {
0N/A return isUserNode;
0N/A }
0N/A
0N/A protected void putSpi(String key, String value) {
0N/A initCacheIfNecessary();
0N/A changeLog.add(new Put(key, value));
0N/A prefsCache.put(key, value);
0N/A }
0N/A
0N/A protected String getSpi(String key) {
0N/A initCacheIfNecessary();
28N/A return prefsCache.get(key);
0N/A }
0N/A
0N/A protected void removeSpi(String key) {
0N/A initCacheIfNecessary();
0N/A changeLog.add(new Remove(key));
0N/A prefsCache.remove(key);
0N/A }
0N/A
0N/A /**
0N/A * Initialize prefsCache if it has yet to be initialized. When this method
0N/A * returns, prefsCache will be non-null. If the data was successfully
0N/A * read from the file, lastSyncTime will be updated. If prefsCache was
0N/A * null, but it was impossible to read the file (because it didn't
0N/A * exist or for any other reason) prefsCache will be initialized to an
0N/A * empty, modifiable Map, and lastSyncTime remain zero.
0N/A */
0N/A private void initCacheIfNecessary() {
0N/A if (prefsCache != null)
0N/A return;
0N/A
0N/A try {
0N/A loadCache();
0N/A } catch(Exception e) {
0N/A // assert lastSyncTime == 0;
3323N/A prefsCache = new TreeMap<>();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Attempt to load prefsCache from the backing store. If the attempt
0N/A * succeeds, lastSyncTime will be updated (the new value will typically
0N/A * correspond to the data loaded into the map, but it may be less,
0N/A * if another VM is updating this node concurrently). If the attempt
0N/A * fails, a BackingStoreException is thrown and both prefsCache and
0N/A * lastSyncTime are unaffected by the call.
0N/A */
0N/A private void loadCache() throws BackingStoreException {
0N/A try {
28N/A AccessController.doPrivileged(
28N/A new PrivilegedExceptionAction<Void>() {
28N/A public Void run() throws BackingStoreException {
3323N/A Map<String, String> m = new TreeMap<>();
0N/A long newLastSyncTime = 0;
0N/A try {
0N/A newLastSyncTime = prefsFile.lastModified();
3646N/A try (FileInputStream fis = new FileInputStream(prefsFile)) {
3646N/A XmlSupport.importMap(fis, m);
3646N/A }
0N/A } catch(Exception e) {
0N/A if (e instanceof InvalidPreferencesFormatException) {
0N/A getLogger().warning("Invalid preferences format in "
0N/A + prefsFile.getPath());
0N/A prefsFile.renameTo( new File(
0N/A prefsFile.getParentFile(),
0N/A "IncorrectFormatPrefs.xml"));
3323N/A m = new TreeMap<>();
0N/A } else if (e instanceof FileNotFoundException) {
0N/A getLogger().warning("Prefs file removed in background "
0N/A + prefsFile.getPath());
0N/A } else {
0N/A throw new BackingStoreException(e);
0N/A }
0N/A }
0N/A // Attempt succeeded; update state
0N/A prefsCache = m;
0N/A lastSyncTime = newLastSyncTime;
0N/A return null;
0N/A }
0N/A });
0N/A } catch (PrivilegedActionException e) {
0N/A throw (BackingStoreException) e.getException();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Attempt to write back prefsCache to the backing store. If the attempt
0N/A * succeeds, lastSyncTime will be updated (the new value will correspond
0N/A * exactly to the data thust written back, as we hold the file lock, which
0N/A * prevents a concurrent write. If the attempt fails, a
0N/A * BackingStoreException is thrown and both the backing store (prefsFile)
0N/A * and lastSyncTime will be unaffected by this call. This call will
0N/A * NEVER leave prefsFile in a corrupt state.
0N/A */
0N/A private void writeBackCache() throws BackingStoreException {
0N/A try {
28N/A AccessController.doPrivileged(
28N/A new PrivilegedExceptionAction<Void>() {
28N/A public Void run() throws BackingStoreException {
0N/A try {
0N/A if (!dir.exists() && !dir.mkdirs())
0N/A throw new BackingStoreException(dir +
0N/A " create failed.");
3646N/A try (FileOutputStream fos = new FileOutputStream(tmpFile)) {
3646N/A XmlSupport.exportMap(fos, prefsCache);
3646N/A }
0N/A if (!tmpFile.renameTo(prefsFile))
0N/A throw new BackingStoreException("Can't rename " +
0N/A tmpFile + " to " + prefsFile);
0N/A } catch(Exception e) {
0N/A if (e instanceof BackingStoreException)
0N/A throw (BackingStoreException)e;
0N/A throw new BackingStoreException(e);
0N/A }
0N/A return null;
0N/A }
0N/A });
0N/A } catch (PrivilegedActionException e) {
0N/A throw (BackingStoreException) e.getException();
0N/A }
0N/A }
0N/A
0N/A protected String[] keysSpi() {
0N/A initCacheIfNecessary();
28N/A return prefsCache.keySet().toArray(new String[prefsCache.size()]);
0N/A }
0N/A
0N/A protected String[] childrenNamesSpi() {
28N/A return AccessController.doPrivileged(
28N/A new PrivilegedAction<String[]>() {
28N/A public String[] run() {
3323N/A List<String> result = new ArrayList<>();
0N/A File[] dirContents = dir.listFiles();
0N/A if (dirContents != null) {
0N/A for (int i = 0; i < dirContents.length; i++)
0N/A if (dirContents[i].isDirectory())
0N/A result.add(nodeName(dirContents[i].getName()));
0N/A }
0N/A return result.toArray(EMPTY_STRING_ARRAY);
0N/A }
0N/A });
0N/A }
0N/A
0N/A private static final String[] EMPTY_STRING_ARRAY = new String[0];
0N/A
0N/A protected AbstractPreferences childSpi(String name) {
0N/A return new FileSystemPreferences(this, name);
0N/A }
0N/A
0N/A public void removeNode() throws BackingStoreException {
0N/A synchronized (isUserNode()? userLockFile: systemLockFile) {
0N/A // to remove a node we need an exclusive lock
0N/A if (!lockFile(false))
0N/A throw(new BackingStoreException("Couldn't get file lock."));
0N/A try {
0N/A super.removeNode();
0N/A } finally {
0N/A unlockFile();
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Called with file lock held (in addition to node locks).
0N/A */
0N/A protected void removeNodeSpi() throws BackingStoreException {
0N/A try {
28N/A AccessController.doPrivileged(
28N/A new PrivilegedExceptionAction<Void>() {
28N/A public Void run() throws BackingStoreException {
0N/A if (changeLog.contains(nodeCreate)) {
0N/A changeLog.remove(nodeCreate);
0N/A nodeCreate = null;
0N/A return null;
0N/A }
0N/A if (!dir.exists())
0N/A return null;
0N/A prefsFile.delete();
0N/A tmpFile.delete();
0N/A // dir should be empty now. If it's not, empty it
0N/A File[] junk = dir.listFiles();
0N/A if (junk.length != 0) {
0N/A getLogger().warning(
0N/A "Found extraneous files when removing node: "
0N/A + Arrays.asList(junk));
0N/A for (int i=0; i<junk.length; i++)
0N/A junk[i].delete();
0N/A }
0N/A if (!dir.delete())
0N/A throw new BackingStoreException("Couldn't delete dir: "
0N/A + dir);
0N/A return null;
0N/A }
0N/A });
0N/A } catch (PrivilegedActionException e) {
0N/A throw (BackingStoreException) e.getException();
0N/A }
0N/A }
0N/A
0N/A public synchronized void sync() throws BackingStoreException {
0N/A boolean userNode = isUserNode();
0N/A boolean shared;
0N/A
0N/A if (userNode) {
0N/A shared = false; /* use exclusive lock for user prefs */
0N/A } else {
0N/A /* if can write to system root, use exclusive lock.
0N/A otherwise use shared lock. */
0N/A shared = !isSystemRootWritable;
0N/A }
0N/A synchronized (isUserNode()? userLockFile:systemLockFile) {
0N/A if (!lockFile(shared))
0N/A throw(new BackingStoreException("Couldn't get file lock."));
0N/A final Long newModTime =
28N/A AccessController.doPrivileged(
28N/A new PrivilegedAction<Long>() {
28N/A public Long run() {
0N/A long nmt;
0N/A if (isUserNode()) {
0N/A nmt = userRootModFile.lastModified();
0N/A isUserRootModified = userRootModTime == nmt;
0N/A } else {
0N/A nmt = systemRootModFile.lastModified();
0N/A isSystemRootModified = systemRootModTime == nmt;
0N/A }
0N/A return new Long(nmt);
0N/A }
0N/A });
0N/A try {
0N/A super.sync();
28N/A AccessController.doPrivileged(new PrivilegedAction<Void>() {
28N/A public Void run() {
0N/A if (isUserNode()) {
0N/A userRootModTime = newModTime.longValue() + 1000;
0N/A userRootModFile.setLastModified(userRootModTime);
0N/A } else {
0N/A systemRootModTime = newModTime.longValue() + 1000;
0N/A systemRootModFile.setLastModified(systemRootModTime);
0N/A }
0N/A return null;
0N/A }
0N/A });
0N/A } finally {
0N/A unlockFile();
0N/A }
0N/A }
0N/A }
0N/A
0N/A protected void syncSpi() throws BackingStoreException {
0N/A try {
28N/A AccessController.doPrivileged(
28N/A new PrivilegedExceptionAction<Void>() {
28N/A public Void run() throws BackingStoreException {
0N/A syncSpiPrivileged();
0N/A return null;
0N/A }
0N/A });
0N/A } catch (PrivilegedActionException e) {
0N/A throw (BackingStoreException) e.getException();
0N/A }
0N/A }
0N/A private void syncSpiPrivileged() throws BackingStoreException {
0N/A if (isRemoved())
0N/A throw new IllegalStateException("Node has been removed");
0N/A if (prefsCache == null)
0N/A return; // We've never been used, don't bother syncing
0N/A long lastModifiedTime;
0N/A if ((isUserNode() ? isUserRootModified : isSystemRootModified)) {
0N/A lastModifiedTime = prefsFile.lastModified();
0N/A if (lastModifiedTime != lastSyncTime) {
0N/A // Prefs at this node were externally modified; read in node and
0N/A // playback any local mods since last sync
0N/A loadCache();
0N/A replayChanges();
0N/A lastSyncTime = lastModifiedTime;
0N/A }
0N/A } else if (lastSyncTime != 0 && !dir.exists()) {
0N/A // This node was removed in the background. Playback any changes
0N/A // against a virgin (empty) Map.
3323N/A prefsCache = new TreeMap<>();
0N/A replayChanges();
0N/A }
0N/A if (!changeLog.isEmpty()) {
0N/A writeBackCache(); // Creates directory & file if necessary
0N/A /*
0N/A * Attempt succeeded; it's barely possible that the call to
0N/A * lastModified might fail (i.e., return 0), but this would not
0N/A * be a disaster, as lastSyncTime is allowed to lag.
0N/A */
0N/A lastModifiedTime = prefsFile.lastModified();
0N/A /* If lastSyncTime did not change, or went back
0N/A * increment by 1 second. Since we hold the lock
0N/A * lastSyncTime always monotonically encreases in the
0N/A * atomic sense.
0N/A */
0N/A if (lastSyncTime <= lastModifiedTime) {
0N/A lastSyncTime = lastModifiedTime + 1000;
0N/A prefsFile.setLastModified(lastSyncTime);
0N/A }
0N/A changeLog.clear();
0N/A }
0N/A }
0N/A
0N/A public void flush() throws BackingStoreException {
0N/A if (isRemoved())
0N/A return;
0N/A sync();
0N/A }
0N/A
0N/A protected void flushSpi() throws BackingStoreException {
0N/A // assert false;
0N/A }
0N/A
0N/A /**
0N/A * Returns true if the specified character is appropriate for use in
0N/A * Unix directory names. A character is appropriate if it's a printable
0N/A * ASCII character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f),
0N/A * dot ('.', 0x2e), or underscore ('_', 0x5f).
0N/A */
0N/A private static boolean isDirChar(char ch) {
0N/A return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_';
0N/A }
0N/A
0N/A /**
0N/A * Returns the directory name corresponding to the specified node name.
0N/A * Generally, this is just the node name. If the node name includes
0N/A * inappropriate characters (as per isDirChar) it is translated to Base64.
0N/A * with the underscore character ('_', 0x5f) prepended.
0N/A */
0N/A private static String dirName(String nodeName) {
0N/A for (int i=0, n=nodeName.length(); i < n; i++)
0N/A if (!isDirChar(nodeName.charAt(i)))
0N/A return "_" + Base64.byteArrayToAltBase64(byteArray(nodeName));
0N/A return nodeName;
0N/A }
0N/A
0N/A /**
0N/A * Translate a string into a byte array by translating each character
0N/A * into two bytes, high-byte first ("big-endian").
0N/A */
0N/A private static byte[] byteArray(String s) {
0N/A int len = s.length();
0N/A byte[] result = new byte[2*len];
0N/A for (int i=0, j=0; i<len; i++) {
0N/A char c = s.charAt(i);
0N/A result[j++] = (byte) (c>>8);
0N/A result[j++] = (byte) c;
0N/A }
0N/A return result;
0N/A }
0N/A
0N/A /**
0N/A * Returns the node name corresponding to the specified directory name.
0N/A * (Inverts the transformation of dirName(String).
0N/A */
0N/A private static String nodeName(String dirName) {
0N/A if (dirName.charAt(0) != '_')
0N/A return dirName;
0N/A byte a[] = Base64.altBase64ToByteArray(dirName.substring(1));
0N/A StringBuffer result = new StringBuffer(a.length/2);
0N/A for (int i = 0; i < a.length; ) {
0N/A int highByte = a[i++] & 0xff;
0N/A int lowByte = a[i++] & 0xff;
0N/A result.append((char) ((highByte << 8) | lowByte));
0N/A }
0N/A return result.toString();
0N/A }
0N/A
0N/A /**
0N/A * Try to acquire the appropriate file lock (user or system). If
0N/A * the initial attempt fails, several more attempts are made using
0N/A * an exponential backoff strategy. If all attempts fail, this method
0N/A * returns false.
0N/A * @throws SecurityException if file access denied.
0N/A */
0N/A private boolean lockFile(boolean shared) throws SecurityException{
0N/A boolean usernode = isUserNode();
0N/A int[] result;
0N/A int errorCode = 0;
0N/A File lockFile = (usernode ? userLockFile : systemLockFile);
0N/A long sleepTime = INIT_SLEEP_TIME;
0N/A for (int i = 0; i < MAX_ATTEMPTS; i++) {
0N/A try {
0N/A int perm = (usernode? USER_READ_WRITE: USER_RW_ALL_READ);
0N/A result = lockFile0(lockFile.getCanonicalPath(), perm, shared);
0N/A
0N/A errorCode = result[ERROR_CODE];
0N/A if (result[LOCK_HANDLE] != 0) {
0N/A if (usernode) {
0N/A userRootLockHandle = result[LOCK_HANDLE];
0N/A } else {
0N/A systemRootLockHandle = result[LOCK_HANDLE];
0N/A }
0N/A return true;
0N/A }
0N/A } catch(IOException e) {
0N/A// // If at first, you don't succeed...
0N/A }
0N/A
0N/A try {
0N/A Thread.sleep(sleepTime);
0N/A } catch(InterruptedException e) {
0N/A checkLockFile0ErrorCode(errorCode);
0N/A return false;
0N/A }
0N/A sleepTime *= 2;
0N/A }
0N/A checkLockFile0ErrorCode(errorCode);
0N/A return false;
0N/A }
0N/A
0N/A /**
0N/A * Checks if unlockFile0() returned an error. Throws a SecurityException,
0N/A * if access denied. Logs a warning otherwise.
0N/A */
0N/A private void checkLockFile0ErrorCode (int errorCode)
0N/A throws SecurityException {
0N/A if (errorCode == EACCES)
0N/A throw new SecurityException("Could not lock " +
0N/A (isUserNode()? "User prefs." : "System prefs.") +
0N/A " Lock file access denied.");
0N/A if (errorCode != EAGAIN)
0N/A getLogger().warning("Could not lock " +
0N/A (isUserNode()? "User prefs. " : "System prefs.") +
0N/A " Unix error code " + errorCode + ".");
0N/A }
0N/A
0N/A /**
0N/A * Locks file using UNIX file locking.
0N/A * @param fileName Absolute file name of the lock file.
0N/A * @return Returns a lock handle, used to unlock the file.
0N/A */
0N/A private static native int[]
0N/A lockFile0(String fileName, int permission, boolean shared);
0N/A
0N/A /**
0N/A * Unlocks file previously locked by lockFile0().
0N/A * @param lockHandle Handle to the file lock.
0N/A * @return Returns zero if OK, UNIX error code if failure.
0N/A */
0N/A private static native int unlockFile0(int lockHandle);
0N/A
0N/A /**
0N/A * Changes UNIX file permissions.
0N/A */
0N/A private static native int chmod(String fileName, int permission);
0N/A
0N/A /**
0N/A * Initial time between lock attempts, in ms. The time is doubled
0N/A * after each failing attempt (except the first).
0N/A */
0N/A private static int INIT_SLEEP_TIME = 50;
0N/A
0N/A /**
0N/A * Maximum number of lock attempts.
0N/A */
0N/A private static int MAX_ATTEMPTS = 5;
0N/A
0N/A /**
0N/A * Release the the appropriate file lock (user or system).
0N/A * @throws SecurityException if file access denied.
0N/A */
0N/A private void unlockFile() {
0N/A int result;
0N/A boolean usernode = isUserNode();
0N/A File lockFile = (usernode ? userLockFile : systemLockFile);
0N/A int lockHandle = ( usernode ? userRootLockHandle:systemRootLockHandle);
0N/A if (lockHandle == 0) {
0N/A getLogger().warning("Unlock: zero lockHandle for " +
0N/A (usernode ? "user":"system") + " preferences.)");
0N/A return;
0N/A }
0N/A result = unlockFile0(lockHandle);
0N/A if (result != 0) {
0N/A getLogger().warning("Could not drop file-lock on " +
0N/A (isUserNode() ? "user" : "system") + " preferences." +
0N/A " Unix error code " + result + ".");
0N/A if (result == EACCES)
0N/A throw new SecurityException("Could not unlock" +
0N/A (isUserNode()? "User prefs." : "System prefs.") +
0N/A " Lock file access denied.");
0N/A }
0N/A if (isUserNode()) {
0N/A userRootLockHandle = 0;
0N/A } else {
0N/A systemRootLockHandle = 0;
0N/A }
0N/A }
0N/A}