1 /*
   2  * Copyright (c) 2000, 2016, 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 import sun.util.logging.PlatformLogger;
  34 
  35 /**
  36  * Preferences implementation for Unix.  Preferences are stored in the file
  37  * system, with one directory per preferences node.  All of the preferences
  38  * at each node are stored in a single file.  Atomic file system operations
  39  * (e.g. File.renameTo) are used to ensure integrity.  An in-memory cache of
  40  * the "explored" portion of the tree is maintained for performance, and
  41  * written back to the disk periodically.  File-locking is used to ensure
  42  * reasonable behavior when multiple VMs are running at the same time.
  43  * (The file lock is obtained only for sync(), flush() and removeNode().)
  44  *
  45  * @author  Josh Bloch
  46  * @see     Preferences
  47  * @since   1.4
  48  */
  49 class FileSystemPreferences extends AbstractPreferences {
  50 
  51     static {
  52         PrivilegedAction<Void> load = () -> {
  53             System.loadLibrary("prefs");
  54             return null;
  55         };
  56         AccessController.doPrivileged(load);
  57     }
  58 
  59     /**
  60      * Sync interval in seconds.
  61      */
  62     private static final int SYNC_INTERVAL = Math.max(1,
  63         AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
  64              Integer.getInteger("java.util.prefs.syncInterval", 30)));
  65 
  66     /**
  67      * Returns logger for error messages. Backing store exceptions are logged at
  68      * WARNING level.
  69      */
  70     private static PlatformLogger getLogger() {
  71         return PlatformLogger.getLogger("java.util.prefs");
  72     }
  73 
  74     /**
  75      * Directory for system preferences.
  76      */
  77     private static File systemRootDir;
  78 
  79     /*
  80      * Flag, indicating whether systemRoot  directory is writable
  81      */
  82     private static boolean isSystemRootWritable;
  83 
  84     /**
  85      * Directory for user preferences.
  86      */
  87     private static File userRootDir;
  88 
  89     /*
  90      * Flag, indicating whether userRoot  directory is writable
  91      */
  92     private static boolean isUserRootWritable;
  93 
  94    /**
  95      * The user root.
  96      */
  97     static Preferences userRoot = null;
  98 
  99     static synchronized Preferences getUserRoot() {
 100         if (userRoot == null) {
 101             setupUserRoot();
 102             userRoot = new FileSystemPreferences(true);
 103         }
 104         return userRoot;
 105     }
 106 
 107     private static void setupUserRoot() {
 108         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 109             public Void run() {
 110                 userRootDir =
 111                       new File(System.getProperty("java.util.prefs.userRoot",
 112                       System.getProperty("user.home")), ".java/.userPrefs");
 113                 // Attempt to create root dir if it does not yet exist.
 114                 if (!userRootDir.exists()) {
 115                     if (userRootDir.mkdirs()) {
 116                         try {
 117                             chmod(userRootDir.getCanonicalPath(), USER_RWX);
 118                         } catch (IOException e) {
 119                             getLogger().warning("Could not change permissions" +
 120                                 " on userRoot directory. ");
 121                         }
 122                         getLogger().info("Created user preferences directory.");
 123                     }
 124                     else
 125                         getLogger().warning("Couldn't create user preferences" +
 126                         " directory. User preferences are unusable.");
 127                 }
 128                 isUserRootWritable = userRootDir.canWrite();
 129                 String USER_NAME = System.getProperty("user.name");
 130                 userLockFile = new File (userRootDir,".user.lock." + USER_NAME);
 131                 userRootModFile = new File (userRootDir,
 132                                                ".userRootModFile." + USER_NAME);
 133                 if (!userRootModFile.exists())
 134                 try {
 135                     // create if does not exist.
 136                     userRootModFile.createNewFile();
 137                     // Only user can read/write userRootModFile.
 138                     int result = chmod(userRootModFile.getCanonicalPath(),
 139                                                                USER_READ_WRITE);
 140                     if (result !=0)
 141                         getLogger().warning("Problem creating userRoot " +
 142                             "mod file. Chmod failed on " +
 143                              userRootModFile.getCanonicalPath() +
 144                              " Unix error code " + result);
 145                 } catch (IOException e) {
 146                     getLogger().warning(e.toString());
 147                 }
 148                 userRootModTime = userRootModFile.lastModified();
 149                 return null;
 150             }
 151         });
 152     }
 153 
 154 
 155     /**
 156      * The system root.
 157      */
 158     static Preferences systemRoot;
 159 
 160     static synchronized Preferences getSystemRoot() {
 161         if (systemRoot == null) {
 162             setupSystemRoot();
 163             systemRoot = new FileSystemPreferences(false);
 164         }
 165         return systemRoot;
 166     }
 167 
 168     private static void setupSystemRoot() {
 169         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 170             public Void run() {
 171                 String systemPrefsDirName =
 172                   System.getProperty("java.util.prefs.systemRoot","/etc/.java");
 173                 systemRootDir =
 174                      new File(systemPrefsDirName, ".systemPrefs");
 175                 // Attempt to create root dir if it does not yet exist.
 176                 if (!systemRootDir.exists()) {
 177                     // system root does not exist in /etc/.java
 178                     // Switching  to java.home
 179                     systemRootDir =
 180                                   new File(System.getProperty("java.home"),
 181                                                             ".systemPrefs");
 182                     if (!systemRootDir.exists()) {
 183                         if (systemRootDir.mkdirs()) {
 184                             getLogger().info(
 185                                 "Created system preferences directory "
 186                                 + "in java.home.");
 187                             try {
 188                                 chmod(systemRootDir.getCanonicalPath(),
 189                                                           USER_RWX_ALL_RX);
 190                             } catch (IOException e) {
 191                             }
 192                         } else {
 193                             getLogger().warning("Could not create "
 194                                 + "system preferences directory. System "
 195                                 + "preferences are unusable.");
 196                         }
 197                     }
 198                 }
 199                 isSystemRootWritable = systemRootDir.canWrite();
 200                 systemLockFile = new File(systemRootDir, ".system.lock");
 201                 systemRootModFile =
 202                                new File (systemRootDir,".systemRootModFile");
 203                 if (!systemRootModFile.exists() && isSystemRootWritable)
 204                 try {
 205                     // create if does not exist.
 206                     systemRootModFile.createNewFile();
 207                     int result = chmod(systemRootModFile.getCanonicalPath(),
 208                                                           USER_RW_ALL_READ);
 209                     if (result !=0)
 210                         getLogger().warning("Chmod failed on " +
 211                                systemRootModFile.getCanonicalPath() +
 212                               " Unix error code " + result);
 213                 } catch (IOException e) { getLogger().warning(e.toString());
 214                 }
 215                 systemRootModTime = systemRootModFile.lastModified();
 216                 return null;
 217             }
 218         });
 219     }
 220 
 221 
 222     /**
 223      * Unix user write/read permission
 224      */
 225     private static final int USER_READ_WRITE = 0600;
 226 
 227     private static final int USER_RW_ALL_READ = 0644;
 228 
 229 
 230     private static final int USER_RWX_ALL_RX = 0755;
 231 
 232     private static final int USER_RWX = 0700;
 233 
 234     /**
 235      * The lock file for the user tree.
 236      */
 237     static File userLockFile;
 238 
 239 
 240 
 241     /**
 242      * The lock file for the system tree.
 243      */
 244     static File systemLockFile;
 245 
 246     /**
 247      * Unix lock handle for userRoot.
 248      * Zero, if unlocked.
 249      */
 250 
 251     private static int userRootLockHandle = 0;
 252 
 253     /**
 254      * Unix lock handle for systemRoot.
 255      * Zero, if unlocked.
 256      */
 257 
 258     private static int systemRootLockHandle = 0;
 259 
 260     /**
 261      * The directory representing this preference node.  There is no guarantee
 262      * that this directory exits, as another VM can delete it at any time
 263      * that it (the other VM) holds the file-lock.  While the root node cannot
 264      * be deleted, it may not yet have been created, or the underlying
 265      * directory could have been deleted accidentally.
 266      */
 267     private final File dir;
 268 
 269     /**
 270      * The file representing this preference node's preferences.
 271      * The file format is undocumented, and subject to change
 272      * from release to release, but I'm sure that you can figure
 273      * it out if you try real hard.
 274      */
 275     private final File prefsFile;
 276 
 277     /**
 278      * A temporary file used for saving changes to preferences.  As part of
 279      * the sync operation, changes are first saved into this file, and then
 280      * atomically renamed to prefsFile.  This results in an atomic state
 281      * change from one valid set of preferences to another.  The
 282      * the file-lock is held for the duration of this transformation.
 283      */
 284     private final File tmpFile;
 285 
 286     /**
 287      * File, which keeps track of global modifications of userRoot.
 288      */
 289     private static  File userRootModFile;
 290 
 291     /**
 292      * Flag, which indicated whether userRoot was modified by another VM
 293      */
 294     private static boolean isUserRootModified = false;
 295 
 296     /**
 297      * Keeps track of userRoot modification time. This time is reset to
 298      * zero after UNIX reboot, and is increased by 1 second each time
 299      * userRoot is modified.
 300      */
 301     private static long userRootModTime;
 302 
 303 
 304     /*
 305      * File, which keeps track of global modifications of systemRoot
 306      */
 307     private static File systemRootModFile;
 308     /*
 309      * Flag, which indicates whether systemRoot was modified by another VM
 310      */
 311     private static boolean isSystemRootModified = false;
 312 
 313     /**
 314      * Keeps track of systemRoot modification time. This time is reset to
 315      * zero after system reboot, and is increased by 1 second each time
 316      * systemRoot is modified.
 317      */
 318     private static long systemRootModTime;
 319 
 320     /**
 321      * Locally cached preferences for this node (includes uncommitted
 322      * changes).  This map is initialized with from disk when the first get or
 323      * put operation occurs on this node.  It is synchronized with the
 324      * corresponding disk file (prefsFile) by the sync operation.  The initial
 325      * value is read *without* acquiring the file-lock.
 326      */
 327     private Map<String, String> prefsCache = null;
 328 
 329     /**
 330      * The last modification time of the file backing this node at the time
 331      * that prefCache was last synchronized (or initially read).  This
 332      * value is set *before* reading the file, so it's conservative; the
 333      * actual timestamp could be (slightly) higher.  A value of zero indicates
 334      * that we were unable to initialize prefsCache from the disk, or
 335      * have not yet attempted to do so.  (If prefsCache is non-null, it
 336      * indicates the former; if it's null, the latter.)
 337      */
 338     private long lastSyncTime = 0;
 339 
 340    /**
 341     * Unix error code for locked file.
 342     */
 343     private static final int EAGAIN = 11;
 344 
 345    /**
 346     * Unix error code for denied access.
 347     */
 348     private static final int EACCES = 13;
 349 
 350     /* Used to interpret results of native functions */
 351     private static final int LOCK_HANDLE = 0;
 352     private static final int ERROR_CODE = 1;
 353 
 354     /**
 355      * A list of all uncommitted preference changes.  The elements in this
 356      * list are of type PrefChange.  If this node is concurrently modified on
 357      * disk by another VM, the two sets of changes are merged when this node
 358      * is sync'ed by overwriting our prefsCache with the preference map last
 359      * written out to disk (by the other VM), and then replaying this change
 360      * log against that map.  The resulting map is then written back
 361      * to the disk.
 362      */
 363     final List<Change> changeLog = new ArrayList<>();
 364 
 365     /**
 366      * Represents a change to a preference.
 367      */
 368     private abstract class Change {
 369         /**
 370          * Reapplies the change to prefsCache.
 371          */
 372         abstract void replay();
 373     };
 374 
 375     /**
 376      * Represents a preference put.
 377      */
 378     private class Put extends Change {
 379         String key, value;
 380 
 381         Put(String key, String value) {
 382             this.key = key;
 383             this.value = value;
 384         }
 385 
 386         void replay() {
 387             prefsCache.put(key, value);
 388         }
 389     }
 390 
 391     /**
 392      * Represents a preference remove.
 393      */
 394     private class Remove extends Change {
 395         String key;
 396 
 397         Remove(String key) {
 398             this.key = key;
 399         }
 400 
 401         void replay() {
 402             prefsCache.remove(key);
 403         }
 404     }
 405 
 406     /**
 407      * Represents the creation of this node.
 408      */
 409     private class NodeCreate extends Change {
 410         /**
 411          * Performs no action, but the presence of this object in changeLog
 412          * will force the node and its ancestors to be made permanent at the
 413          * next sync.
 414          */
 415         void replay() {
 416         }
 417     }
 418 
 419     /**
 420      * NodeCreate object for this node.
 421      */
 422     NodeCreate nodeCreate = null;
 423 
 424     /**
 425      * Replay changeLog against prefsCache.
 426      */
 427     private void replayChanges() {
 428         for (int i = 0, n = changeLog.size(); i<n; i++)
 429             changeLog.get(i).replay();
 430     }
 431 
 432     private static Timer syncTimer = new Timer(true); // Daemon Thread
 433 
 434     static {
 435         // Add periodic timer task to periodically sync cached prefs
 436         syncTimer.schedule(new TimerTask() {
 437             public void run() {
 438                 syncWorld();
 439             }
 440         }, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000);
 441 
 442         // Add shutdown hook to flush cached prefs on normal termination
 443         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 444             public Void run() {
 445                 Runtime.getRuntime().addShutdownHook(
 446                     new Thread(null, null, "Sync Timer Thread", 0, false) {
 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 }