Save This Page
Home » openjdk-7 » java » util » prefs » [javadoc | source]
    1   /*
    2    * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   
   26   package java.util.prefs;
   27   import java.util;
   28   import java.io;
   29   import java.security.AccessController;
   30   import java.security.PrivilegedAction;
   31   import java.security.PrivilegedExceptionAction;
   32   import java.security.PrivilegedActionException;
   33   
   34   import sun.util.logging.PlatformLogger;
   35   
   36   /**
   37    * Preferences implementation for Unix.  Preferences are stored in the file
   38    * system, with one directory per preferences node.  All of the preferences
   39    * at each node are stored in a single file.  Atomic file system operations
   40    * (e.g. File.renameTo) are used to ensure integrity.  An in-memory cache of
   41    * the "explored" portion of the tree is maintained for performance, and
   42    * written back to the disk periodically.  File-locking is used to ensure
   43    * reasonable behavior when multiple VMs are running at the same time.
   44    * (The file lock is obtained only for sync(), flush() and removeNode().)
   45    *
   46    * @author  Josh Bloch
   47    * @see     Preferences
   48    * @since   1.4
   49    */
   50   class FileSystemPreferences extends AbstractPreferences {
   51       /**
   52        * Sync interval in seconds.
   53        */
   54       private static final int SYNC_INTERVAL = Math.max(1,
   55           Integer.parseInt(
   56               AccessController.doPrivileged(
   57                   new sun.security.action.GetPropertyAction(
   58                       "java.util.prefs.syncInterval", "30"))));
   59   
   60       /**
   61        * Returns logger for error messages. Backing store exceptions are logged at
   62        * WARNING level.
   63        */
   64       private static PlatformLogger getLogger() {
   65           return PlatformLogger.getLogger("java.util.prefs");
   66       }
   67   
   68       /**
   69        * Directory for system preferences.
   70        */
   71       private static File systemRootDir;
   72   
   73       /*
   74        * Flag, indicating whether systemRoot  directory is writable
   75        */
   76       private static boolean isSystemRootWritable;
   77   
   78       /**
   79        * Directory for user preferences.
   80        */
   81       private static File userRootDir;
   82   
   83       /*
   84        * Flag, indicating whether userRoot  directory is writable
   85        */
   86       private static boolean isUserRootWritable;
   87   
   88      /**
   89        * The user root.
   90        */
   91       static Preferences userRoot = null;
   92   
   93       static synchronized Preferences getUserRoot() {
   94           if (userRoot == null) {
   95               setupUserRoot();
   96               userRoot = new FileSystemPreferences(true);
   97           }
   98           return userRoot;
   99       }
  100   
  101       private static void setupUserRoot() {
  102           AccessController.doPrivileged(new PrivilegedAction<Void>() {
  103               public Void run() {
  104                   userRootDir =
  105                         new File(System.getProperty("java.util.prefs.userRoot",
  106                         System.getProperty("user.home")), ".java/.userPrefs");
  107                   // Attempt to create root dir if it does not yet exist.
  108                   if (!userRootDir.exists()) {
  109                       if (userRootDir.mkdirs()) {
  110                           try {
  111                               chmod(userRootDir.getCanonicalPath(), USER_RWX);
  112                           } catch (IOException e) {
  113                               getLogger().warning("Could not change permissions" +
  114                                   " on userRoot directory. ");
  115                           }
  116                           getLogger().info("Created user preferences directory.");
  117                       }
  118                       else
  119                           getLogger().warning("Couldn't create user preferences" +
  120                           " directory. User preferences are unusable.");
  121                   }
  122                   isUserRootWritable = userRootDir.canWrite();
  123                   String USER_NAME = System.getProperty("user.name");
  124                   userLockFile = new File (userRootDir,".user.lock." + USER_NAME);
  125                   userRootModFile = new File (userRootDir,
  126                                                  ".userRootModFile." + USER_NAME);
  127                   if (!userRootModFile.exists())
  128                   try {
  129                       // create if does not exist.
  130                       userRootModFile.createNewFile();
  131                       // Only user can read/write userRootModFile.
  132                       int result = chmod(userRootModFile.getCanonicalPath(),
  133                                                                  USER_READ_WRITE);
  134                       if (result !=0)
  135                           getLogger().warning("Problem creating userRoot " +
  136                               "mod file. Chmod failed on " +
  137                                userRootModFile.getCanonicalPath() +
  138                                " Unix error code " + result);
  139                   } catch (IOException e) {
  140                       getLogger().warning(e.toString());
  141                   }
  142                   userRootModTime = userRootModFile.lastModified();
  143                   return null;
  144               }
  145           });
  146       }
  147   
  148   
  149       /**
  150        * The system root.
  151        */
  152       static Preferences systemRoot;
  153   
  154       static synchronized Preferences getSystemRoot() {
  155           if (systemRoot == null) {
  156               setupSystemRoot();
  157               systemRoot = new FileSystemPreferences(false);
  158           }
  159           return systemRoot;
  160       }
  161   
  162       private static void setupSystemRoot() {
  163           AccessController.doPrivileged(new PrivilegedAction<Void>() {
  164               public Void run() {
  165                   String systemPrefsDirName =
  166                     System.getProperty("java.util.prefs.systemRoot","/etc/.java");
  167                   systemRootDir =
  168                        new File(systemPrefsDirName, ".systemPrefs");
  169                   // Attempt to create root dir if it does not yet exist.
  170                   if (!systemRootDir.exists()) {
  171                       // system root does not exist in /etc/.java
  172                       // Switching  to java.home
  173                       systemRootDir =
  174                                     new File(System.getProperty("java.home"),
  175                                                               ".systemPrefs");
  176                       if (!systemRootDir.exists()) {
  177                           if (systemRootDir.mkdirs()) {
  178                               getLogger().info(
  179                                   "Created system preferences directory "
  180                                   + "in java.home.");
  181                               try {
  182                                   chmod(systemRootDir.getCanonicalPath(),
  183                                                             USER_RWX_ALL_RX);
  184                               } catch (IOException e) {
  185                               }
  186                           } else {
  187                               getLogger().warning("Could not create "
  188                                   + "system preferences directory. System "
  189                                   + "preferences are unusable.");
  190                           }
  191                       }
  192                   }
  193                   isSystemRootWritable = systemRootDir.canWrite();
  194                   systemLockFile = new File(systemRootDir, ".system.lock");
  195                   systemRootModFile =
  196                                  new File (systemRootDir,".systemRootModFile");
  197                   if (!systemRootModFile.exists() && isSystemRootWritable)
  198                   try {
  199                       // create if does not exist.
  200                       systemRootModFile.createNewFile();
  201                       int result = chmod(systemRootModFile.getCanonicalPath(),
  202                                                             USER_RW_ALL_READ);
  203                       if (result !=0)
  204                           getLogger().warning("Chmod failed on " +
  205                                  systemRootModFile.getCanonicalPath() +
  206                                 " Unix error code " + result);
  207                   } catch (IOException e) { getLogger().warning(e.toString());
  208                   }
  209                   systemRootModTime = systemRootModFile.lastModified();
  210                   return null;
  211               }
  212           });
  213       }
  214   
  215   
  216       /**
  217        * Unix user write/read permission
  218        */
  219       private static final int USER_READ_WRITE = 0600;
  220   
  221       private static final int USER_RW_ALL_READ = 0644;
  222   
  223   
  224       private static final int USER_RWX_ALL_RX = 0755;
  225   
  226       private static final int USER_RWX = 0700;
  227   
  228       /**
  229        * The lock file for the user tree.
  230        */
  231       static File userLockFile;
  232   
  233   
  234   
  235       /**
  236        * The lock file for the system tree.
  237        */
  238       static File systemLockFile;
  239   
  240       /**
  241        * Unix lock handle for userRoot.
  242        * Zero, if unlocked.
  243        */
  244   
  245       private static int userRootLockHandle = 0;
  246   
  247       /**
  248        * Unix lock handle for systemRoot.
  249        * Zero, if unlocked.
  250        */
  251   
  252       private static int systemRootLockHandle = 0;
  253   
  254       /**
  255        * The directory representing this preference node.  There is no guarantee
  256        * that this directory exits, as another VM can delete it at any time
  257        * that it (the other VM) holds the file-lock.  While the root node cannot
  258        * be deleted, it may not yet have been created, or the underlying
  259        * directory could have been deleted accidentally.
  260        */
  261       private final File dir;
  262   
  263       /**
  264        * The file representing this preference node's preferences.
  265        * The file format is undocumented, and subject to change
  266        * from release to release, but I'm sure that you can figure
  267        * it out if you try real hard.
  268        */
  269       private final File prefsFile;
  270   
  271       /**
  272        * A temporary file used for saving changes to preferences.  As part of
  273        * the sync operation, changes are first saved into this file, and then
  274        * atomically renamed to prefsFile.  This results in an atomic state
  275        * change from one valid set of preferences to another.  The
  276        * the file-lock is held for the duration of this transformation.
  277        */
  278       private final File tmpFile;
  279   
  280       /**
  281        * File, which keeps track of global modifications of userRoot.
  282        */
  283       private static  File userRootModFile;
  284   
  285       /**
  286        * Flag, which indicated whether userRoot was modified by another VM
  287        */
  288       private static boolean isUserRootModified = false;
  289   
  290       /**
  291        * Keeps track of userRoot modification time. This time is reset to
  292        * zero after UNIX reboot, and is increased by 1 second each time
  293        * userRoot is modified.
  294        */
  295       private static long userRootModTime;
  296   
  297   
  298       /*
  299        * File, which keeps track of global modifications of systemRoot
  300        */
  301       private static File systemRootModFile;
  302       /*
  303        * Flag, which indicates whether systemRoot was modified by another VM
  304        */
  305       private static boolean isSystemRootModified = false;
  306   
  307       /**
  308        * Keeps track of systemRoot modification time. This time is reset to
  309        * zero after system reboot, and is increased by 1 second each time
  310        * systemRoot is modified.
  311        */
  312       private static long systemRootModTime;
  313   
  314       /**
  315        * Locally cached preferences for this node (includes uncommitted
  316        * changes).  This map is initialized with from disk when the first get or
  317        * put operation occurs on this node.  It is synchronized with the
  318        * corresponding disk file (prefsFile) by the sync operation.  The initial
  319        * value is read *without* acquiring the file-lock.
  320        */
  321       private Map<String, String> prefsCache = null;
  322   
  323       /**
  324        * The last modification time of the file backing this node at the time
  325        * that prefCache was last synchronized (or initially read).  This
  326        * value is set *before* reading the file, so it's conservative; the
  327        * actual timestamp could be (slightly) higher.  A value of zero indicates
  328        * that we were unable to initialize prefsCache from the disk, or
  329        * have not yet attempted to do so.  (If prefsCache is non-null, it
  330        * indicates the former; if it's null, the latter.)
  331        */
  332       private long lastSyncTime = 0;
  333   
  334      /**
  335       * Unix error code for locked file.
  336       */
  337       private static final int EAGAIN = 11;
  338   
  339      /**
  340       * Unix error code for denied access.
  341       */
  342       private static final int EACCES = 13;
  343   
  344       /* Used to interpret results of native functions */
  345       private static final int LOCK_HANDLE = 0;
  346       private static final int ERROR_CODE = 1;
  347   
  348       /**
  349        * A list of all uncommitted preference changes.  The elements in this
  350        * list are of type PrefChange.  If this node is concurrently modified on
  351        * disk by another VM, the two sets of changes are merged when this node
  352        * is sync'ed by overwriting our prefsCache with the preference map last
  353        * written out to disk (by the other VM), and then replaying this change
  354        * log against that map.  The resulting map is then written back
  355        * to the disk.
  356        */
  357       final List<Change> changeLog = new ArrayList<>();
  358   
  359       /**
  360        * Represents a change to a preference.
  361        */
  362       private abstract class Change {
  363           /**
  364            * Reapplies the change to prefsCache.
  365            */
  366           abstract void replay();
  367       };
  368   
  369       /**
  370        * Represents a preference put.
  371        */
  372       private class Put extends Change {
  373           String key, value;
  374   
  375           Put(String key, String value) {
  376               this.key = key;
  377               this.value = value;
  378           }
  379   
  380           void replay() {
  381               prefsCache.put(key, value);
  382           }
  383       }
  384   
  385       /**
  386        * Represents a preference remove.
  387        */
  388       private class Remove extends Change {
  389           String key;
  390   
  391           Remove(String key) {
  392               this.key = key;
  393           }
  394   
  395           void replay() {
  396               prefsCache.remove(key);
  397           }
  398       }
  399   
  400       /**
  401        * Represents the creation of this node.
  402        */
  403       private class NodeCreate extends Change {
  404           /**
  405            * Performs no action, but the presence of this object in changeLog
  406            * will force the node and its ancestors to be made permanent at the
  407            * next sync.
  408            */
  409           void replay() {
  410           }
  411       }
  412   
  413       /**
  414        * NodeCreate object for this node.
  415        */
  416       NodeCreate nodeCreate = null;
  417   
  418       /**
  419        * Replay changeLog against prefsCache.
  420        */
  421       private void replayChanges() {
  422           for (int i = 0, n = changeLog.size(); i<n; i++)
  423               changeLog.get(i).replay();
  424       }
  425   
  426       private static Timer syncTimer = new Timer(true); // Daemon Thread
  427   
  428       static {
  429           // Add periodic timer task to periodically sync cached prefs
  430           syncTimer.schedule(new TimerTask() {
  431               public void run() {
  432                   syncWorld();
  433               }
  434           }, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000);
  435   
  436           // Add shutdown hook to flush cached prefs on normal termination
  437           AccessController.doPrivileged(new PrivilegedAction<Void>() {
  438               public Void run() {
  439                   Runtime.getRuntime().addShutdownHook(new Thread() {
  440                       public void run() {
  441                           syncTimer.cancel();
  442                           syncWorld();
  443                       }
  444                   });
  445                   return null;
  446               }
  447           });
  448       }
  449   
  450       private static void syncWorld() {
  451           /*
  452            * Synchronization necessary because userRoot and systemRoot are
  453            * lazily initialized.
  454            */
  455           Preferences userRt;
  456           Preferences systemRt;
  457           synchronized(FileSystemPreferences.class) {
  458               userRt   = userRoot;
  459               systemRt = systemRoot;
  460           }
  461   
  462           try {
  463               if (userRt != null)
  464                   userRt.flush();
  465           } catch(BackingStoreException e) {
  466               getLogger().warning("Couldn't flush user prefs: " + e);
  467           }
  468   
  469           try {
  470               if (systemRt != null)
  471                   systemRt.flush();
  472           } catch(BackingStoreException e) {
  473               getLogger().warning("Couldn't flush system prefs: " + e);
  474           }
  475       }
  476   
  477       private final boolean isUserNode;
  478   
  479       /**
  480        * Special constructor for roots (both user and system).  This constructor
  481        * will only be called twice, by the static initializer.
  482        */
  483       private FileSystemPreferences(boolean user) {
  484           super(null, "");
  485           isUserNode = user;
  486           dir = (user ? userRootDir: systemRootDir);
  487           prefsFile = new File(dir, "prefs.xml");
  488           tmpFile   = new File(dir, "prefs.tmp");
  489       }
  490   
  491       /**
  492        * Construct a new FileSystemPreferences instance with the specified
  493        * parent node and name.  This constructor, called from childSpi,
  494        * is used to make every node except for the two //roots.
  495        */
  496       private FileSystemPreferences(FileSystemPreferences parent, String name) {
  497           super(parent, name);
  498           isUserNode = parent.isUserNode;
  499           dir  = new File(parent.dir, dirName(name));
  500           prefsFile = new File(dir, "prefs.xml");
  501           tmpFile  = new File(dir, "prefs.tmp");
  502           AccessController.doPrivileged(new PrivilegedAction<Void>() {
  503               public Void run() {
  504                   newNode = !dir.exists();
  505                   return null;
  506               }
  507           });
  508           if (newNode) {
  509               // These 2 things guarantee node will get wrtten at next flush/sync
  510               prefsCache = new TreeMap<>();
  511               nodeCreate = new NodeCreate();
  512               changeLog.add(nodeCreate);
  513           }
  514       }
  515   
  516       public boolean isUserNode() {
  517           return isUserNode;
  518       }
  519   
  520       protected void putSpi(String key, String value) {
  521           initCacheIfNecessary();
  522           changeLog.add(new Put(key, value));
  523           prefsCache.put(key, value);
  524       }
  525   
  526       protected String getSpi(String key) {
  527           initCacheIfNecessary();
  528           return prefsCache.get(key);
  529       }
  530   
  531       protected void removeSpi(String key) {
  532           initCacheIfNecessary();
  533           changeLog.add(new Remove(key));
  534           prefsCache.remove(key);
  535       }
  536   
  537       /**
  538        * Initialize prefsCache if it has yet to be initialized.  When this method
  539        * returns, prefsCache will be non-null.  If the data was successfully
  540        * read from the file, lastSyncTime will be updated.  If prefsCache was
  541        * null, but it was impossible to read the file (because it didn't
  542        * exist or for any other reason) prefsCache will be initialized to an
  543        * empty, modifiable Map, and lastSyncTime remain zero.
  544        */
  545       private void initCacheIfNecessary() {
  546           if (prefsCache != null)
  547               return;
  548   
  549           try {
  550               loadCache();
  551           } catch(Exception e) {
  552               // assert lastSyncTime == 0;
  553               prefsCache = new TreeMap<>();
  554           }
  555       }
  556   
  557       /**
  558        * Attempt to load prefsCache from the backing store.  If the attempt
  559        * succeeds, lastSyncTime will be updated (the new value will typically
  560        * correspond to the data loaded into the map, but it may be less,
  561        * if another VM is updating this node concurrently).  If the attempt
  562        * fails, a BackingStoreException is thrown and both prefsCache and
  563        * lastSyncTime are unaffected by the call.
  564        */
  565       private void loadCache() throws BackingStoreException {
  566           try {
  567               AccessController.doPrivileged(
  568                   new PrivilegedExceptionAction<Void>() {
  569                   public Void run() throws BackingStoreException {
  570                       Map<String, String> m = new TreeMap<>();
  571                       long newLastSyncTime = 0;
  572                       try {
  573                           newLastSyncTime = prefsFile.lastModified();
  574                           try (FileInputStream fis = new FileInputStream(prefsFile)) {
  575                               XmlSupport.importMap(fis, m);
  576                           }
  577                       } catch(Exception e) {
  578                           if (e instanceof InvalidPreferencesFormatException) {
  579                               getLogger().warning("Invalid preferences format in "
  580                                                           +  prefsFile.getPath());
  581                               prefsFile.renameTo( new File(
  582                                                       prefsFile.getParentFile(),
  583                                                     "IncorrectFormatPrefs.xml"));
  584                               m = new TreeMap<>();
  585                           } else if (e instanceof FileNotFoundException) {
  586                           getLogger().warning("Prefs file removed in background "
  587                                              + prefsFile.getPath());
  588                           } else {
  589                               throw new BackingStoreException(e);
  590                           }
  591                       }
  592                       // Attempt succeeded; update state
  593                       prefsCache = m;
  594                       lastSyncTime = newLastSyncTime;
  595                       return null;
  596                   }
  597               });
  598           } catch (PrivilegedActionException e) {
  599               throw (BackingStoreException) e.getException();
  600           }
  601       }
  602   
  603       /**
  604        * Attempt to write back prefsCache to the backing store.  If the attempt
  605        * succeeds, lastSyncTime will be updated (the new value will correspond
  606        * exactly to the data thust written back, as we hold the file lock, which
  607        * prevents a concurrent write.  If the attempt fails, a
  608        * BackingStoreException is thrown and both the backing store (prefsFile)
  609        * and lastSyncTime will be unaffected by this call.  This call will
  610        * NEVER leave prefsFile in a corrupt state.
  611        */
  612       private void writeBackCache() throws BackingStoreException {
  613           try {
  614               AccessController.doPrivileged(
  615                   new PrivilegedExceptionAction<Void>() {
  616                   public Void run() throws BackingStoreException {
  617                       try {
  618                           if (!dir.exists() && !dir.mkdirs())
  619                               throw new BackingStoreException(dir +
  620                                                                " create failed.");
  621                           try (FileOutputStream fos = new FileOutputStream(tmpFile)) {
  622                               XmlSupport.exportMap(fos, prefsCache);
  623                           }
  624                           if (!tmpFile.renameTo(prefsFile))
  625                               throw new BackingStoreException("Can't rename " +
  626                               tmpFile + " to " + prefsFile);
  627                       } catch(Exception e) {
  628                           if (e instanceof BackingStoreException)
  629                               throw (BackingStoreException)e;
  630                           throw new BackingStoreException(e);
  631                       }
  632                       return null;
  633                   }
  634               });
  635           } catch (PrivilegedActionException e) {
  636               throw (BackingStoreException) e.getException();
  637           }
  638       }
  639   
  640       protected String[] keysSpi() {
  641           initCacheIfNecessary();
  642           return prefsCache.keySet().toArray(new String[prefsCache.size()]);
  643       }
  644   
  645       protected String[] childrenNamesSpi() {
  646           return AccessController.doPrivileged(
  647               new PrivilegedAction<String[]>() {
  648                   public String[] run() {
  649                       List<String> result = new ArrayList<>();
  650                       File[] dirContents = dir.listFiles();
  651                       if (dirContents != null) {
  652                           for (int i = 0; i < dirContents.length; i++)
  653                               if (dirContents[i].isDirectory())
  654                                   result.add(nodeName(dirContents[i].getName()));
  655                       }
  656                       return result.toArray(EMPTY_STRING_ARRAY);
  657                  }
  658               });
  659       }
  660   
  661       private static final String[] EMPTY_STRING_ARRAY = new String[0];
  662   
  663       protected AbstractPreferences childSpi(String name) {
  664           return new FileSystemPreferences(this, name);
  665       }
  666   
  667       public void removeNode() throws BackingStoreException {
  668           synchronized (isUserNode()? userLockFile: systemLockFile) {
  669               // to remove a node we need an exclusive lock
  670               if (!lockFile(false))
  671                   throw(new BackingStoreException("Couldn't get file lock."));
  672              try {
  673                   super.removeNode();
  674              } finally {
  675                   unlockFile();
  676              }
  677           }
  678       }
  679   
  680       /**
  681        * Called with file lock held (in addition to node locks).
  682        */
  683       protected void removeNodeSpi() throws BackingStoreException {
  684           try {
  685               AccessController.doPrivileged(
  686                   new PrivilegedExceptionAction<Void>() {
  687                   public Void run() throws BackingStoreException {
  688                       if (changeLog.contains(nodeCreate)) {
  689                           changeLog.remove(nodeCreate);
  690                           nodeCreate = null;
  691                           return null;
  692                       }
  693                       if (!dir.exists())
  694                           return null;
  695                       prefsFile.delete();
  696                       tmpFile.delete();
  697                       // dir should be empty now.  If it's not, empty it
  698                       File[] junk = dir.listFiles();
  699                       if (junk.length != 0) {
  700                           getLogger().warning(
  701                              "Found extraneous files when removing node: "
  702                               + Arrays.asList(junk));
  703                           for (int i=0; i<junk.length; i++)
  704                               junk[i].delete();
  705                       }
  706                       if (!dir.delete())
  707                           throw new BackingStoreException("Couldn't delete dir: "
  708                                                                            + dir);
  709                       return null;
  710                   }
  711               });
  712           } catch (PrivilegedActionException e) {
  713               throw (BackingStoreException) e.getException();
  714           }
  715       }
  716   
  717       public synchronized void sync() throws BackingStoreException {
  718           boolean userNode = isUserNode();
  719           boolean shared;
  720   
  721           if (userNode) {
  722               shared = false; /* use exclusive lock for user prefs */
  723           } else {
  724               /* if can write to system root, use exclusive lock.
  725                  otherwise use shared lock. */
  726               shared = !isSystemRootWritable;
  727           }
  728           synchronized (isUserNode()? userLockFile:systemLockFile) {
  729              if (!lockFile(shared))
  730                  throw(new BackingStoreException("Couldn't get file lock."));
  731              final Long newModTime =
  732                   AccessController.doPrivileged(
  733                       new PrivilegedAction<Long>() {
  734                  public Long run() {
  735                      long nmt;
  736                      if (isUserNode()) {
  737                          nmt = userRootModFile.lastModified();
  738                          isUserRootModified = userRootModTime == nmt;
  739                      } else {
  740                          nmt = systemRootModFile.lastModified();
  741                          isSystemRootModified = systemRootModTime == nmt;
  742                      }
  743                      return new Long(nmt);
  744                  }
  745              });
  746              try {
  747                  super.sync();
  748                  AccessController.doPrivileged(new PrivilegedAction<Void>() {
  749                      public Void run() {
  750                      if (isUserNode()) {
  751                          userRootModTime = newModTime.longValue() + 1000;
  752                          userRootModFile.setLastModified(userRootModTime);
  753                      } else {
  754                          systemRootModTime = newModTime.longValue() + 1000;
  755                          systemRootModFile.setLastModified(systemRootModTime);
  756                      }
  757                      return null;
  758                      }
  759                  });
  760              } finally {
  761                   unlockFile();
  762              }
  763           }
  764       }
  765   
  766       protected void syncSpi() throws BackingStoreException {
  767           try {
  768               AccessController.doPrivileged(
  769                   new PrivilegedExceptionAction<Void>() {
  770                   public Void run() throws BackingStoreException {
  771                       syncSpiPrivileged();
  772                       return null;
  773                   }
  774               });
  775           } catch (PrivilegedActionException e) {
  776               throw (BackingStoreException) e.getException();
  777           }
  778       }
  779       private void syncSpiPrivileged() throws BackingStoreException {
  780           if (isRemoved())
  781               throw new IllegalStateException("Node has been removed");
  782           if (prefsCache == null)
  783               return;  // We've never been used, don't bother syncing
  784           long lastModifiedTime;
  785           if ((isUserNode() ? isUserRootModified : isSystemRootModified)) {
  786               lastModifiedTime = prefsFile.lastModified();
  787               if (lastModifiedTime  != lastSyncTime) {
  788                   // Prefs at this node were externally modified; read in node and
  789                   // playback any local mods since last sync
  790                   loadCache();
  791                   replayChanges();
  792                   lastSyncTime = lastModifiedTime;
  793               }
  794           } else if (lastSyncTime != 0 && !dir.exists()) {
  795               // This node was removed in the background.  Playback any changes
  796               // against a virgin (empty) Map.
  797               prefsCache = new TreeMap<>();
  798               replayChanges();
  799           }
  800           if (!changeLog.isEmpty()) {
  801               writeBackCache();  // Creates directory & file if necessary
  802              /*
  803               * Attempt succeeded; it's barely possible that the call to
  804               * lastModified might fail (i.e., return 0), but this would not
  805               * be a disaster, as lastSyncTime is allowed to lag.
  806               */
  807               lastModifiedTime = prefsFile.lastModified();
  808               /* If lastSyncTime did not change, or went back
  809                * increment by 1 second. Since we hold the lock
  810                * lastSyncTime always monotonically encreases in the
  811                * atomic sense.
  812                */
  813               if (lastSyncTime <= lastModifiedTime) {
  814                   lastSyncTime = lastModifiedTime + 1000;
  815                   prefsFile.setLastModified(lastSyncTime);
  816               }
  817               changeLog.clear();
  818           }
  819       }
  820   
  821       public void flush() throws BackingStoreException {
  822           if (isRemoved())
  823               return;
  824           sync();
  825       }
  826   
  827       protected void flushSpi() throws BackingStoreException {
  828           // assert false;
  829       }
  830   
  831       /**
  832        * Returns true if the specified character is appropriate for use in
  833        * Unix directory names.  A character is appropriate if it's a printable
  834        * ASCII character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f),
  835        * dot ('.', 0x2e), or underscore ('_', 0x5f).
  836        */
  837       private static boolean isDirChar(char ch) {
  838           return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_';
  839       }
  840   
  841       /**
  842        * Returns the directory name corresponding to the specified node name.
  843        * Generally, this is just the node name.  If the node name includes
  844        * inappropriate characters (as per isDirChar) it is translated to Base64.
  845        * with the underscore  character ('_', 0x5f) prepended.
  846        */
  847       private static String dirName(String nodeName) {
  848           for (int i=0, n=nodeName.length(); i < n; i++)
  849               if (!isDirChar(nodeName.charAt(i)))
  850                   return "_" + Base64.byteArrayToAltBase64(byteArray(nodeName));
  851           return nodeName;
  852       }
  853   
  854       /**
  855        * Translate a string into a byte array by translating each character
  856        * into two bytes, high-byte first ("big-endian").
  857        */
  858       private static byte[] byteArray(String s) {
  859           int len = s.length();
  860           byte[] result = new byte[2*len];
  861           for (int i=0, j=0; i<len; i++) {
  862               char c = s.charAt(i);
  863               result[j++] = (byte) (c>>8);
  864               result[j++] = (byte) c;
  865           }
  866           return result;
  867       }
  868   
  869       /**
  870        * Returns the node name corresponding to the specified directory name.
  871    * (Inverts the transformation of dirName(String).
  872        */
  873       private static String nodeName(String dirName) {
  874           if (dirName.charAt(0) != '_')
  875               return dirName;
  876           byte a[] = Base64.altBase64ToByteArray(dirName.substring(1));
  877           StringBuffer result = new StringBuffer(a.length/2);
  878           for (int i = 0; i < a.length; ) {
  879               int highByte = a[i++] & 0xff;
  880               int lowByte =  a[i++] & 0xff;
  881               result.append((char) ((highByte << 8) | lowByte));
  882           }
  883           return result.toString();
  884       }
  885   
  886       /**
  887        * Try to acquire the appropriate file lock (user or system).  If
  888        * the initial attempt fails, several more attempts are made using
  889        * an exponential backoff strategy.  If all attempts fail, this method
  890        * returns false.
  891        * @throws SecurityException if file access denied.
  892        */
  893       private boolean lockFile(boolean shared) throws SecurityException{
  894           boolean usernode = isUserNode();
  895           int[] result;
  896           int errorCode = 0;
  897           File lockFile = (usernode ? userLockFile : systemLockFile);
  898           long sleepTime = INIT_SLEEP_TIME;
  899           for (int i = 0; i < MAX_ATTEMPTS; i++) {
  900               try {
  901                     int perm = (usernode? USER_READ_WRITE: USER_RW_ALL_READ);
  902                     result = lockFile0(lockFile.getCanonicalPath(), perm, shared);
  903   
  904                     errorCode = result[ERROR_CODE];
  905                     if (result[LOCK_HANDLE] != 0) {
  906                        if (usernode) {
  907                            userRootLockHandle = result[LOCK_HANDLE];
  908                        } else {
  909                            systemRootLockHandle = result[LOCK_HANDLE];
  910                        }
  911                        return true;
  912                     }
  913               } catch(IOException e) {
  914   //                // If at first, you don't succeed...
  915               }
  916   
  917               try {
  918                   Thread.sleep(sleepTime);
  919               } catch(InterruptedException e) {
  920                   checkLockFile0ErrorCode(errorCode);
  921                   return false;
  922               }
  923               sleepTime *= 2;
  924           }
  925           checkLockFile0ErrorCode(errorCode);
  926           return false;
  927       }
  928   
  929       /**
  930        * Checks if unlockFile0() returned an error. Throws a SecurityException,
  931        * if access denied. Logs a warning otherwise.
  932        */
  933       private void checkLockFile0ErrorCode (int errorCode)
  934                                                         throws SecurityException {
  935           if (errorCode == EACCES)
  936               throw new SecurityException("Could not lock " +
  937               (isUserNode()? "User prefs." : "System prefs.") +
  938                " Lock file access denied.");
  939           if (errorCode != EAGAIN)
  940               getLogger().warning("Could not lock " +
  941                                (isUserNode()? "User prefs. " : "System prefs.") +
  942                                " Unix error code " + errorCode + ".");
  943       }
  944   
  945       /**
  946        * Locks file using UNIX file locking.
  947        * @param fileName Absolute file name of the lock file.
  948        * @return Returns a lock handle, used to unlock the file.
  949        */
  950       private static native int[]
  951               lockFile0(String fileName, int permission, boolean shared);
  952   
  953       /**
  954        * Unlocks file previously locked by lockFile0().
  955        * @param lockHandle Handle to the file lock.
  956        * @return Returns zero if OK, UNIX error code if failure.
  957        */
  958       private  static native int unlockFile0(int lockHandle);
  959   
  960       /**
  961        * Changes UNIX file permissions.
  962        */
  963       private static native int chmod(String fileName, int permission);
  964   
  965       /**
  966        * Initial time between lock attempts, in ms.  The time is doubled
  967        * after each failing attempt (except the first).
  968        */
  969       private static int INIT_SLEEP_TIME = 50;
  970   
  971       /**
  972        * Maximum number of lock attempts.
  973        */
  974       private static int MAX_ATTEMPTS = 5;
  975   
  976       /**
  977        * Release the the appropriate file lock (user or system).
  978        * @throws SecurityException if file access denied.
  979        */
  980       private void unlockFile() {
  981           int result;
  982           boolean usernode = isUserNode();
  983           File lockFile = (usernode ? userLockFile : systemLockFile);
  984           int lockHandle = ( usernode ? userRootLockHandle:systemRootLockHandle);
  985           if (lockHandle == 0) {
  986               getLogger().warning("Unlock: zero lockHandle for " +
  987                              (usernode ? "user":"system") + " preferences.)");
  988               return;
  989           }
  990           result = unlockFile0(lockHandle);
  991           if (result != 0) {
  992               getLogger().warning("Could not drop file-lock on " +
  993               (isUserNode() ? "user" : "system") + " preferences." +
  994               " Unix error code " + result + ".");
  995               if (result == EACCES)
  996                   throw new SecurityException("Could not unlock" +
  997                   (isUserNode()? "User prefs." : "System prefs.") +
  998                   " Lock file access denied.");
  999           }
 1000           if (isUserNode()) {
 1001               userRootLockHandle = 0;
 1002           } else {
 1003               systemRootLockHandle = 0;
 1004           }
 1005       }
 1006   }

Save This Page
Home » openjdk-7 » java » util » prefs » [javadoc | source]