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