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