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