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