1 /* 2 * Copyright (c) 2000, 2016, 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 import sun.util.logging.PlatformLogger; 34 35 /** 36 * Preferences implementation for Unix. Preferences are stored in the file 37 * system, with one directory per preferences node. All of the preferences 38 * at each node are stored in a single file. Atomic file system operations 39 * (e.g. File.renameTo) are used to ensure integrity. An in-memory cache of 40 * the "explored" portion of the tree is maintained for performance, and 41 * written back to the disk periodically. File-locking is used to ensure 42 * reasonable behavior when multiple VMs are running at the same time. 43 * (The file lock is obtained only for sync(), flush() and removeNode().) 44 * 45 * @author Josh Bloch 46 * @see Preferences 47 * @since 1.4 48 */ 49 class FileSystemPreferences extends AbstractPreferences { 50 51 static { 52 PrivilegedAction<Void> load = () -> { 53 System.loadLibrary("prefs"); 54 return null; 55 }; 56 AccessController.doPrivileged(load); 57 } 58 59 /** 60 * Sync interval in seconds. 61 */ 62 private static final int SYNC_INTERVAL = Math.max(1, 63 AccessController.doPrivileged((PrivilegedAction<Integer>) () -> 64 Integer.getInteger("java.util.prefs.syncInterval", 30))); 65 66 /** 67 * Returns logger for error messages. Backing store exceptions are logged at 68 * WARNING level. 69 */ getLogger()70 private static PlatformLogger getLogger() { 71 return PlatformLogger.getLogger("java.util.prefs"); 72 } 73 74 /** 75 * Directory for system preferences. 76 */ 77 private static File systemRootDir; 78 79 /* 80 * Flag, indicating whether systemRoot directory is writable 81 */ 82 private static boolean isSystemRootWritable; 83 84 /** 85 * Directory for user preferences. 86 */ 87 private static File userRootDir; 88 89 /* 90 * Flag, indicating whether userRoot directory is writable 91 */ 92 private static boolean isUserRootWritable; 93 94 /** 95 * The user root. 96 */ 97 private static volatile Preferences userRoot; 98 getUserRoot()99 static Preferences getUserRoot() { 100 Preferences root = userRoot; 101 if (root == null) { 102 synchronized (FileSystemPreferences.class) { 103 root = userRoot; 104 if (root == null) { 105 setupUserRoot(); 106 userRoot = root = new FileSystemPreferences(true); 107 } 108 } 109 } 110 return root; 111 } 112 setupUserRoot()113 private static void setupUserRoot() { 114 AccessController.doPrivileged(new PrivilegedAction<Void>() { 115 public Void run() { 116 userRootDir = 117 new File(System.getProperty("java.util.prefs.userRoot", 118 System.getProperty("user.home")), ".java/.userPrefs"); 119 // Attempt to create root dir if it does not yet exist. 120 if (!userRootDir.exists()) { 121 if (userRootDir.mkdirs()) { 122 try { 123 chmod(userRootDir.getCanonicalPath(), USER_RWX); 124 } catch (IOException e) { 125 getLogger().warning("Could not change permissions" + 126 " on userRoot directory. "); 127 } 128 getLogger().info("Created user preferences directory."); 129 } 130 else 131 getLogger().warning("Couldn't create user preferences" + 132 " directory. User preferences are unusable."); 133 } 134 isUserRootWritable = userRootDir.canWrite(); 135 String USER_NAME = System.getProperty("user.name"); 136 userLockFile = new File (userRootDir,".user.lock." + USER_NAME); 137 userRootModFile = new File (userRootDir, 138 ".userRootModFile." + USER_NAME); 139 if (!userRootModFile.exists()) 140 try { 141 // create if does not exist. 142 userRootModFile.createNewFile(); 143 // Only user can read/write userRootModFile. 144 int result = chmod(userRootModFile.getCanonicalPath(), 145 USER_READ_WRITE); 146 if (result !=0) 147 getLogger().warning("Problem creating userRoot " + 148 "mod file. Chmod failed on " + 149 userRootModFile.getCanonicalPath() + 150 " Unix error code " + result); 151 } catch (IOException e) { 152 getLogger().warning(e.toString()); 153 } 154 userRootModTime = userRootModFile.lastModified(); 155 return null; 156 } 157 }); 158 } 159 160 161 /** 162 * The system root. 163 */ 164 private static volatile Preferences systemRoot; 165 getSystemRoot()166 static Preferences getSystemRoot() { 167 Preferences root = systemRoot; 168 if (root == null) { 169 synchronized (FileSystemPreferences.class) { 170 root = systemRoot; 171 if (root == null) { 172 setupSystemRoot(); 173 systemRoot = root = new FileSystemPreferences(false); 174 } 175 } 176 } 177 return root; 178 } 179 setupSystemRoot()180 private static void setupSystemRoot() { 181 AccessController.doPrivileged(new PrivilegedAction<Void>() { 182 public Void run() { 183 String systemPrefsDirName = 184 System.getProperty("java.util.prefs.systemRoot","/etc/.java"); 185 systemRootDir = 186 new File(systemPrefsDirName, ".systemPrefs"); 187 // Attempt to create root dir if it does not yet exist. 188 if (!systemRootDir.exists()) { 189 // system root does not exist in /etc/.java 190 // Switching to java.home 191 systemRootDir = 192 new File(System.getProperty("java.home"), 193 ".systemPrefs"); 194 if (!systemRootDir.exists()) { 195 if (systemRootDir.mkdirs()) { 196 getLogger().info( 197 "Created system preferences directory " 198 + "in java.home."); 199 try { 200 chmod(systemRootDir.getCanonicalPath(), 201 USER_RWX_ALL_RX); 202 } catch (IOException e) { 203 } 204 } else { 205 getLogger().warning("Could not create " 206 + "system preferences directory. System " 207 + "preferences are unusable."); 208 } 209 } 210 } 211 isSystemRootWritable = systemRootDir.canWrite(); 212 systemLockFile = new File(systemRootDir, ".system.lock"); 213 systemRootModFile = 214 new File (systemRootDir,".systemRootModFile"); 215 if (!systemRootModFile.exists() && isSystemRootWritable) 216 try { 217 // create if does not exist. 218 systemRootModFile.createNewFile(); 219 int result = chmod(systemRootModFile.getCanonicalPath(), 220 USER_RW_ALL_READ); 221 if (result !=0) 222 getLogger().warning("Chmod failed on " + 223 systemRootModFile.getCanonicalPath() + 224 " Unix error code " + result); 225 } catch (IOException e) { getLogger().warning(e.toString()); 226 } 227 systemRootModTime = systemRootModFile.lastModified(); 228 return null; 229 } 230 }); 231 } 232 233 234 /** 235 * Unix user write/read permission 236 */ 237 private static final int USER_READ_WRITE = 0600; 238 239 private static final int USER_RW_ALL_READ = 0644; 240 241 242 private static final int USER_RWX_ALL_RX = 0755; 243 244 private static final int USER_RWX = 0700; 245 246 /** 247 * The lock file for the user tree. 248 */ 249 static File userLockFile; 250 251 252 253 /** 254 * The lock file for the system tree. 255 */ 256 static File systemLockFile; 257 258 /** 259 * Unix lock handle for userRoot. 260 * Zero, if unlocked. 261 */ 262 263 private static int userRootLockHandle = 0; 264 265 /** 266 * Unix lock handle for systemRoot. 267 * Zero, if unlocked. 268 */ 269 270 private static int systemRootLockHandle = 0; 271 272 /** 273 * The directory representing this preference node. There is no guarantee 274 * that this directory exits, as another VM can delete it at any time 275 * that it (the other VM) holds the file-lock. While the root node cannot 276 * be deleted, it may not yet have been created, or the underlying 277 * directory could have been deleted accidentally. 278 */ 279 private final File dir; 280 281 /** 282 * The file representing this preference node's preferences. 283 * The file format is undocumented, and subject to change 284 * from release to release, but I'm sure that you can figure 285 * it out if you try real hard. 286 */ 287 private final File prefsFile; 288 289 /** 290 * A temporary file used for saving changes to preferences. As part of 291 * the sync operation, changes are first saved into this file, and then 292 * atomically renamed to prefsFile. This results in an atomic state 293 * change from one valid set of preferences to another. The 294 * the file-lock is held for the duration of this transformation. 295 */ 296 private final File tmpFile; 297 298 /** 299 * File, which keeps track of global modifications of userRoot. 300 */ 301 private static File userRootModFile; 302 303 /** 304 * Flag, which indicated whether userRoot was modified by another VM 305 */ 306 private static boolean isUserRootModified = false; 307 308 /** 309 * Keeps track of userRoot modification time. This time is reset to 310 * zero after UNIX reboot, and is increased by 1 second each time 311 * userRoot is modified. 312 */ 313 private static long userRootModTime; 314 315 316 /* 317 * File, which keeps track of global modifications of systemRoot 318 */ 319 private static File systemRootModFile; 320 /* 321 * Flag, which indicates whether systemRoot was modified by another VM 322 */ 323 private static boolean isSystemRootModified = false; 324 325 /** 326 * Keeps track of systemRoot modification time. This time is reset to 327 * zero after system reboot, and is increased by 1 second each time 328 * systemRoot is modified. 329 */ 330 private static long systemRootModTime; 331 332 /** 333 * Locally cached preferences for this node (includes uncommitted 334 * changes). This map is initialized with from disk when the first get or 335 * put operation occurs on this node. It is synchronized with the 336 * corresponding disk file (prefsFile) by the sync operation. The initial 337 * value is read *without* acquiring the file-lock. 338 */ 339 private Map<String, String> prefsCache = null; 340 341 /** 342 * The last modification time of the file backing this node at the time 343 * that prefCache was last synchronized (or initially read). This 344 * value is set *before* reading the file, so it's conservative; the 345 * actual timestamp could be (slightly) higher. A value of zero indicates 346 * that we were unable to initialize prefsCache from the disk, or 347 * have not yet attempted to do so. (If prefsCache is non-null, it 348 * indicates the former; if it's null, the latter.) 349 */ 350 private long lastSyncTime = 0; 351 352 /** 353 * Unix error code for locked file. 354 */ 355 private static final int EAGAIN = 11; 356 357 /** 358 * Unix error code for denied access. 359 */ 360 private static final int EACCES = 13; 361 362 /* Used to interpret results of native functions */ 363 private static final int LOCK_HANDLE = 0; 364 private static final int ERROR_CODE = 1; 365 366 /** 367 * A list of all uncommitted preference changes. The elements in this 368 * list are of type PrefChange. If this node is concurrently modified on 369 * disk by another VM, the two sets of changes are merged when this node 370 * is sync'ed by overwriting our prefsCache with the preference map last 371 * written out to disk (by the other VM), and then replaying this change 372 * log against that map. The resulting map is then written back 373 * to the disk. 374 */ 375 final List<Change> changeLog = new ArrayList<>(); 376 377 /** 378 * Represents a change to a preference. 379 */ 380 private abstract class Change { 381 /** 382 * Reapplies the change to prefsCache. 383 */ replay()384 abstract void replay(); 385 }; 386 387 /** 388 * Represents a preference put. 389 */ 390 private class Put extends Change { 391 String key, value; 392 Put(String key, String value)393 Put(String key, String value) { 394 this.key = key; 395 this.value = value; 396 } 397 replay()398 void replay() { 399 prefsCache.put(key, value); 400 } 401 } 402 403 /** 404 * Represents a preference remove. 405 */ 406 private class Remove extends Change { 407 String key; 408 Remove(String key)409 Remove(String key) { 410 this.key = key; 411 } 412 replay()413 void replay() { 414 prefsCache.remove(key); 415 } 416 } 417 418 /** 419 * Represents the creation of this node. 420 */ 421 private class NodeCreate extends Change { 422 /** 423 * Performs no action, but the presence of this object in changeLog 424 * will force the node and its ancestors to be made permanent at the 425 * next sync. 426 */ replay()427 void replay() { 428 } 429 } 430 431 /** 432 * NodeCreate object for this node. 433 */ 434 NodeCreate nodeCreate = null; 435 436 /** 437 * Replay changeLog against prefsCache. 438 */ replayChanges()439 private void replayChanges() { 440 for (int i = 0, n = changeLog.size(); i<n; i++) 441 changeLog.get(i).replay(); 442 } 443 444 private static Timer syncTimer = new Timer(true); // Daemon Thread 445 446 static { 447 // Add periodic timer task to periodically sync cached prefs syncTimer.schedule(new TimerTask() { public void run() { syncWorld(); } }, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000)448 syncTimer.schedule(new TimerTask() { 449 public void run() { 450 syncWorld(); 451 } 452 }, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000); 453 454 // Add shutdown hook to flush cached prefs on normal termination AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { Runtime.getRuntime().addShutdownHook( new Thread(null, null, R, 0, false) { public void run() { syncTimer.cancel(); syncWorld(); } }); return null; } })455 AccessController.doPrivileged(new PrivilegedAction<Void>() { 456 public Void run() { 457 Runtime.getRuntime().addShutdownHook( 458 new Thread(null, null, "Sync Timer Thread", 0, false) { 459 public void run() { 460 syncTimer.cancel(); 461 syncWorld(); 462 } 463 }); 464 return null; 465 } 466 }); 467 } 468 syncWorld()469 private static void syncWorld() { 470 /* 471 * Synchronization necessary because userRoot and systemRoot are 472 * lazily initialized. 473 */ 474 Preferences userRt; 475 Preferences systemRt; 476 synchronized(FileSystemPreferences.class) { 477 userRt = userRoot; 478 systemRt = systemRoot; 479 } 480 481 try { 482 if (userRt != null) 483 userRt.flush(); 484 } catch(BackingStoreException e) { 485 getLogger().warning("Couldn't flush user prefs: " + e); 486 } 487 488 try { 489 if (systemRt != null) 490 systemRt.flush(); 491 } catch(BackingStoreException e) { 492 getLogger().warning("Couldn't flush system prefs: " + e); 493 } 494 } 495 496 private final boolean isUserNode; 497 498 /** 499 * Special constructor for roots (both user and system). This constructor 500 * will only be called twice, by the static initializer. 501 */ FileSystemPreferences(boolean user)502 private FileSystemPreferences(boolean user) { 503 super(null, ""); 504 isUserNode = user; 505 dir = (user ? userRootDir: systemRootDir); 506 prefsFile = new File(dir, "prefs.xml"); 507 tmpFile = new File(dir, "prefs.tmp"); 508 } 509 510 /** 511 * Construct a new FileSystemPreferences instance with the specified 512 * parent node and name. This constructor, called from childSpi, 513 * is used to make every node except for the two //roots. 514 */ FileSystemPreferences(FileSystemPreferences parent, String name)515 private FileSystemPreferences(FileSystemPreferences parent, String name) { 516 super(parent, name); 517 isUserNode = parent.isUserNode; 518 dir = new File(parent.dir, dirName(name)); 519 prefsFile = new File(dir, "prefs.xml"); 520 tmpFile = new File(dir, "prefs.tmp"); 521 AccessController.doPrivileged(new PrivilegedAction<Void>() { 522 public Void run() { 523 newNode = !dir.exists(); 524 return null; 525 } 526 }); 527 if (newNode) { 528 // These 2 things guarantee node will get wrtten at next flush/sync 529 prefsCache = new TreeMap<>(); 530 nodeCreate = new NodeCreate(); 531 changeLog.add(nodeCreate); 532 } 533 } 534 isUserNode()535 public boolean isUserNode() { 536 return isUserNode; 537 } 538 putSpi(String key, String value)539 protected void putSpi(String key, String value) { 540 initCacheIfNecessary(); 541 changeLog.add(new Put(key, value)); 542 prefsCache.put(key, value); 543 } 544 getSpi(String key)545 protected String getSpi(String key) { 546 initCacheIfNecessary(); 547 return prefsCache.get(key); 548 } 549 removeSpi(String key)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 */ initCacheIfNecessary()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 */ loadCache()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 */ writeBackCache()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 keysSpi()659 protected String[] keysSpi() { 660 initCacheIfNecessary(); 661 return prefsCache.keySet().toArray(new String[prefsCache.size()]); 662 } 663 childrenNamesSpi()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 } 1026