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