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 }