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 }