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