FileSystemPreferences.java 36.4 KB
Newer Older
D
duke 已提交
1
/*
2
 * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
D
duke 已提交
3 4 5 6
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
7
 * published by the Free Software Foundation.  Oracle designates this
D
duke 已提交
8
 * particular file as subject to the "Classpath" exception as provided
9
 * by Oracle in the LICENSE file that accompanied this code.
D
duke 已提交
10 11 12 13 14 15 16 17 18 19 20
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
21 22 23
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
D
duke 已提交
24 25 26 27 28 29 30 31 32 33
 */

package java.util.prefs;
import java.util.*;
import java.io.*;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;

34
import sun.util.logging.PlatformLogger;
D
duke 已提交
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

/**
 * Preferences implementation for Unix.  Preferences are stored in the file
 * system, with one directory per preferences node.  All of the preferences
 * at each node are stored in a single file.  Atomic file system operations
 * (e.g. File.renameTo) are used to ensure integrity.  An in-memory cache of
 * the "explored" portion of the tree is maintained for performance, and
 * written back to the disk periodically.  File-locking is used to ensure
 * reasonable behavior when multiple VMs are running at the same time.
 * (The file lock is obtained only for sync(), flush() and removeNode().)
 *
 * @author  Josh Bloch
 * @see     Preferences
 * @since   1.4
 */
class FileSystemPreferences extends AbstractPreferences {
    /**
     * Sync interval in seconds.
     */
    private static final int SYNC_INTERVAL = Math.max(1,
55 56 57 58
        Integer.parseInt(
            AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    "java.util.prefs.syncInterval", "30"))));
D
duke 已提交
59 60 61 62 63

    /**
     * Returns logger for error messages. Backing store exceptions are logged at
     * WARNING level.
     */
64 65
    private static PlatformLogger getLogger() {
        return PlatformLogger.getLogger("java.util.prefs");
D
duke 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
    }

    /**
     * Directory for system preferences.
     */
    private static File systemRootDir;

    /*
     * Flag, indicating whether systemRoot  directory is writable
     */
    private static boolean isSystemRootWritable;

    /**
     * Directory for user preferences.
     */
    private static File userRootDir;

    /*
     * Flag, indicating whether userRoot  directory is writable
     */
    private static boolean isUserRootWritable;

   /**
     * The user root.
     */
    static Preferences userRoot = null;

    static synchronized Preferences getUserRoot() {
        if (userRoot == null) {
            setupUserRoot();
            userRoot = new FileSystemPreferences(true);
        }
        return userRoot;
    }

    private static void setupUserRoot() {
102 103
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
D
duke 已提交
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
                userRootDir =
                      new File(System.getProperty("java.util.prefs.userRoot",
                      System.getProperty("user.home")), ".java/.userPrefs");
                // Attempt to create root dir if it does not yet exist.
                if (!userRootDir.exists()) {
                    if (userRootDir.mkdirs()) {
                        try {
                            chmod(userRootDir.getCanonicalPath(), USER_RWX);
                        } catch (IOException e) {
                            getLogger().warning("Could not change permissions" +
                                " on userRoot directory. ");
                        }
                        getLogger().info("Created user preferences directory.");
                    }
                    else
                        getLogger().warning("Couldn't create user preferences" +
                        " directory. User preferences are unusable.");
                }
                isUserRootWritable = userRootDir.canWrite();
                String USER_NAME = System.getProperty("user.name");
                userLockFile = new File (userRootDir,".user.lock." + USER_NAME);
                userRootModFile = new File (userRootDir,
                                               ".userRootModFile." + USER_NAME);
                if (!userRootModFile.exists())
                try {
                    // create if does not exist.
                    userRootModFile.createNewFile();
                    // Only user can read/write userRootModFile.
                    int result = chmod(userRootModFile.getCanonicalPath(),
                                                               USER_READ_WRITE);
                    if (result !=0)
                        getLogger().warning("Problem creating userRoot " +
                            "mod file. Chmod failed on " +
                             userRootModFile.getCanonicalPath() +
                             " Unix error code " + result);
                } catch (IOException e) {
                    getLogger().warning(e.toString());
                }
                userRootModTime = userRootModFile.lastModified();
                return null;
            }
        });
    }


    /**
     * The system root.
     */
    static Preferences systemRoot;

    static synchronized Preferences getSystemRoot() {
        if (systemRoot == null) {
            setupSystemRoot();
            systemRoot = new FileSystemPreferences(false);
        }
        return systemRoot;
    }

    private static void setupSystemRoot() {
163 164 165
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                String systemPrefsDirName =
D
duke 已提交
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
                  System.getProperty("java.util.prefs.systemRoot","/etc/.java");
                systemRootDir =
                     new File(systemPrefsDirName, ".systemPrefs");
                // Attempt to create root dir if it does not yet exist.
                if (!systemRootDir.exists()) {
                    // system root does not exist in /etc/.java
                    // Switching  to java.home
                    systemRootDir =
                                  new File(System.getProperty("java.home"),
                                                            ".systemPrefs");
                    if (!systemRootDir.exists()) {
                        if (systemRootDir.mkdirs()) {
                            getLogger().info(
                                "Created system preferences directory "
                                + "in java.home.");
                            try {
                                chmod(systemRootDir.getCanonicalPath(),
                                                          USER_RWX_ALL_RX);
                            } catch (IOException e) {
                            }
                        } else {
                            getLogger().warning("Could not create "
                                + "system preferences directory. System "
                                + "preferences are unusable.");
                        }
                    }
                }
                isSystemRootWritable = systemRootDir.canWrite();
                systemLockFile = new File(systemRootDir, ".system.lock");
                systemRootModFile =
                               new File (systemRootDir,".systemRootModFile");
                if (!systemRootModFile.exists() && isSystemRootWritable)
                try {
                    // create if does not exist.
                    systemRootModFile.createNewFile();
                    int result = chmod(systemRootModFile.getCanonicalPath(),
                                                          USER_RW_ALL_READ);
                    if (result !=0)
                        getLogger().warning("Chmod failed on " +
                               systemRootModFile.getCanonicalPath() +
                              " Unix error code " + result);
                } catch (IOException e) { getLogger().warning(e.toString());
                }
                systemRootModTime = systemRootModFile.lastModified();
                return null;
            }
        });
    }


    /**
     * Unix user write/read permission
     */
    private static final int USER_READ_WRITE = 0600;

    private static final int USER_RW_ALL_READ = 0644;


    private static final int USER_RWX_ALL_RX = 0755;

    private static final int USER_RWX = 0700;

    /**
     * The lock file for the user tree.
     */
    static File userLockFile;



    /**
     * The lock file for the system tree.
     */
    static File systemLockFile;

    /**
     * Unix lock handle for userRoot.
     * Zero, if unlocked.
     */

    private static int userRootLockHandle = 0;

    /**
     * Unix lock handle for systemRoot.
     * Zero, if unlocked.
     */

    private static int systemRootLockHandle = 0;

    /**
     * The directory representing this preference node.  There is no guarantee
     * that this directory exits, as another VM can delete it at any time
     * that it (the other VM) holds the file-lock.  While the root node cannot
     * be deleted, it may not yet have been created, or the underlying
     * directory could have been deleted accidentally.
     */
    private final File dir;

    /**
     * The file representing this preference node's preferences.
     * The file format is undocumented, and subject to change
     * from release to release, but I'm sure that you can figure
     * it out if you try real hard.
     */
    private final File prefsFile;

    /**
     * A temporary file used for saving changes to preferences.  As part of
     * the sync operation, changes are first saved into this file, and then
     * atomically renamed to prefsFile.  This results in an atomic state
     * change from one valid set of preferences to another.  The
     * the file-lock is held for the duration of this transformation.
     */
    private final File tmpFile;

    /**
     * File, which keeps track of global modifications of userRoot.
     */
    private static  File userRootModFile;

    /**
     * Flag, which indicated whether userRoot was modified by another VM
     */
    private static boolean isUserRootModified = false;

    /**
     * Keeps track of userRoot modification time. This time is reset to
     * zero after UNIX reboot, and is increased by 1 second each time
     * userRoot is modified.
     */
    private static long userRootModTime;


    /*
     * File, which keeps track of global modifications of systemRoot
     */
    private static File systemRootModFile;
    /*
     * Flag, which indicates whether systemRoot was modified by another VM
     */
    private static boolean isSystemRootModified = false;

    /**
     * Keeps track of systemRoot modification time. This time is reset to
     * zero after system reboot, and is increased by 1 second each time
     * systemRoot is modified.
     */
    private static long systemRootModTime;

    /**
     * Locally cached preferences for this node (includes uncommitted
     * changes).  This map is initialized with from disk when the first get or
     * put operation occurs on this node.  It is synchronized with the
     * corresponding disk file (prefsFile) by the sync operation.  The initial
     * value is read *without* acquiring the file-lock.
     */
321
    private Map<String, String> prefsCache = null;
D
duke 已提交
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356

    /**
     * The last modification time of the file backing this node at the time
     * that prefCache was last synchronized (or initially read).  This
     * value is set *before* reading the file, so it's conservative; the
     * actual timestamp could be (slightly) higher.  A value of zero indicates
     * that we were unable to initialize prefsCache from the disk, or
     * have not yet attempted to do so.  (If prefsCache is non-null, it
     * indicates the former; if it's null, the latter.)
     */
    private long lastSyncTime = 0;

   /**
    * Unix error code for locked file.
    */
    private static final int EAGAIN = 11;

   /**
    * Unix error code for denied access.
    */
    private static final int EACCES = 13;

    /* Used to interpret results of native functions */
    private static final int LOCK_HANDLE = 0;
    private static final int ERROR_CODE = 1;

    /**
     * A list of all uncommitted preference changes.  The elements in this
     * list are of type PrefChange.  If this node is concurrently modified on
     * disk by another VM, the two sets of changes are merged when this node
     * is sync'ed by overwriting our prefsCache with the preference map last
     * written out to disk (by the other VM), and then replaying this change
     * log against that map.  The resulting map is then written back
     * to the disk.
     */
357
    final List<Change> changeLog = new ArrayList<>();
D
duke 已提交
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422

    /**
     * Represents a change to a preference.
     */
    private abstract class Change {
        /**
         * Reapplies the change to prefsCache.
         */
        abstract void replay();
    };

    /**
     * Represents a preference put.
     */
    private class Put extends Change {
        String key, value;

        Put(String key, String value) {
            this.key = key;
            this.value = value;
        }

        void replay() {
            prefsCache.put(key, value);
        }
    }

    /**
     * Represents a preference remove.
     */
    private class Remove extends Change {
        String key;

        Remove(String key) {
            this.key = key;
        }

        void replay() {
            prefsCache.remove(key);
        }
    }

    /**
     * Represents the creation of this node.
     */
    private class NodeCreate extends Change {
        /**
         * Performs no action, but the presence of this object in changeLog
         * will force the node and its ancestors to be made permanent at the
         * next sync.
         */
        void replay() {
        }
    }

    /**
     * NodeCreate object for this node.
     */
    NodeCreate nodeCreate = null;

    /**
     * Replay changeLog against prefsCache.
     */
    private void replayChanges() {
        for (int i = 0, n = changeLog.size(); i<n; i++)
423
            changeLog.get(i).replay();
D
duke 已提交
424 425 426 427 428 429 430 431 432 433 434 435 436
    }

    private static Timer syncTimer = new Timer(true); // Daemon Thread

    static {
        // Add periodic timer task to periodically sync cached prefs
        syncTimer.schedule(new TimerTask() {
            public void run() {
                syncWorld();
            }
        }, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000);

        // Add shutdown hook to flush cached prefs on normal termination
437 438
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
D
duke 已提交
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    public void run() {
                        syncTimer.cancel();
                        syncWorld();
                    }
                });
                return null;
            }
        });
    }

    private static void syncWorld() {
        /*
         * Synchronization necessary because userRoot and systemRoot are
         * lazily initialized.
         */
        Preferences userRt;
        Preferences systemRt;
        synchronized(FileSystemPreferences.class) {
            userRt   = userRoot;
            systemRt = systemRoot;
        }

        try {
            if (userRt != null)
                userRt.flush();
        } catch(BackingStoreException e) {
            getLogger().warning("Couldn't flush user prefs: " + e);
        }

        try {
            if (systemRt != null)
                systemRt.flush();
        } catch(BackingStoreException e) {
            getLogger().warning("Couldn't flush system prefs: " + e);
        }
    }

    private final boolean isUserNode;

    /**
     * Special constructor for roots (both user and system).  This constructor
     * will only be called twice, by the static initializer.
     */
    private FileSystemPreferences(boolean user) {
        super(null, "");
        isUserNode = user;
        dir = (user ? userRootDir: systemRootDir);
        prefsFile = new File(dir, "prefs.xml");
        tmpFile   = new File(dir, "prefs.tmp");
    }

    /**
     * Construct a new FileSystemPreferences instance with the specified
     * parent node and name.  This constructor, called from childSpi,
     * is used to make every node except for the two //roots.
     */
    private FileSystemPreferences(FileSystemPreferences parent, String name) {
        super(parent, name);
        isUserNode = parent.isUserNode;
        dir  = new File(parent.dir, dirName(name));
        prefsFile = new File(dir, "prefs.xml");
        tmpFile  = new File(dir, "prefs.tmp");
502 503
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
D
duke 已提交
504 505 506 507 508 509
                newNode = !dir.exists();
                return null;
            }
        });
        if (newNode) {
            // These 2 things guarantee node will get wrtten at next flush/sync
510
            prefsCache = new TreeMap<>();
D
duke 已提交
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
            nodeCreate = new NodeCreate();
            changeLog.add(nodeCreate);
        }
    }

    public boolean isUserNode() {
        return isUserNode;
    }

    protected void putSpi(String key, String value) {
        initCacheIfNecessary();
        changeLog.add(new Put(key, value));
        prefsCache.put(key, value);
    }

    protected String getSpi(String key) {
        initCacheIfNecessary();
528
        return prefsCache.get(key);
D
duke 已提交
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
    }

    protected void removeSpi(String key) {
        initCacheIfNecessary();
        changeLog.add(new Remove(key));
        prefsCache.remove(key);
    }

    /**
     * Initialize prefsCache if it has yet to be initialized.  When this method
     * returns, prefsCache will be non-null.  If the data was successfully
     * read from the file, lastSyncTime will be updated.  If prefsCache was
     * null, but it was impossible to read the file (because it didn't
     * exist or for any other reason) prefsCache will be initialized to an
     * empty, modifiable Map, and lastSyncTime remain zero.
     */
    private void initCacheIfNecessary() {
        if (prefsCache != null)
            return;

        try {
            loadCache();
        } catch(Exception e) {
            // assert lastSyncTime == 0;
553
            prefsCache = new TreeMap<>();
D
duke 已提交
554 555 556 557 558 559 560 561 562 563 564 565 566
        }
    }

    /**
     * Attempt to load prefsCache from the backing store.  If the attempt
     * succeeds, lastSyncTime will be updated (the new value will typically
     * correspond to the data loaded into the map, but it may be less,
     * if another VM is updating this node concurrently).  If the attempt
     * fails, a BackingStoreException is thrown and both prefsCache and
     * lastSyncTime are unaffected by the call.
     */
    private void loadCache() throws BackingStoreException {
        try {
567 568 569
            AccessController.doPrivileged(
                new PrivilegedExceptionAction<Void>() {
                public Void run() throws BackingStoreException {
570
                    Map<String, String> m = new TreeMap<>();
D
duke 已提交
571 572 573 574 575 576 577 578 579 580 581 582 583
                    long newLastSyncTime = 0;
                    try {
                        newLastSyncTime = prefsFile.lastModified();
                        FileInputStream fis = new FileInputStream(prefsFile);
                        XmlSupport.importMap(fis, m);
                        fis.close();
                    } catch(Exception e) {
                        if (e instanceof InvalidPreferencesFormatException) {
                            getLogger().warning("Invalid preferences format in "
                                                        +  prefsFile.getPath());
                            prefsFile.renameTo( new File(
                                                    prefsFile.getParentFile(),
                                                  "IncorrectFormatPrefs.xml"));
584
                            m = new TreeMap<>();
D
duke 已提交
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
                        } else if (e instanceof FileNotFoundException) {
                        getLogger().warning("Prefs file removed in background "
                                           + prefsFile.getPath());
                        } else {
                            throw new BackingStoreException(e);
                        }
                    }
                    // Attempt succeeded; update state
                    prefsCache = m;
                    lastSyncTime = newLastSyncTime;
                    return null;
                }
            });
        } catch (PrivilegedActionException e) {
            throw (BackingStoreException) e.getException();
        }
    }

    /**
     * Attempt to write back prefsCache to the backing store.  If the attempt
     * succeeds, lastSyncTime will be updated (the new value will correspond
     * exactly to the data thust written back, as we hold the file lock, which
     * prevents a concurrent write.  If the attempt fails, a
     * BackingStoreException is thrown and both the backing store (prefsFile)
     * and lastSyncTime will be unaffected by this call.  This call will
     * NEVER leave prefsFile in a corrupt state.
     */
    private void writeBackCache() throws BackingStoreException {
        try {
614 615 616
            AccessController.doPrivileged(
                new PrivilegedExceptionAction<Void>() {
                public Void run() throws BackingStoreException {
D
duke 已提交
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
                    try {
                        if (!dir.exists() && !dir.mkdirs())
                            throw new BackingStoreException(dir +
                                                             " create failed.");
                        FileOutputStream fos = new FileOutputStream(tmpFile);
                        XmlSupport.exportMap(fos, prefsCache);
                        fos.close();
                        if (!tmpFile.renameTo(prefsFile))
                            throw new BackingStoreException("Can't rename " +
                            tmpFile + " to " + prefsFile);
                    } catch(Exception e) {
                        if (e instanceof BackingStoreException)
                            throw (BackingStoreException)e;
                        throw new BackingStoreException(e);
                    }
                    return null;
                }
            });
        } catch (PrivilegedActionException e) {
            throw (BackingStoreException) e.getException();
        }
    }

    protected String[] keysSpi() {
        initCacheIfNecessary();
642
        return prefsCache.keySet().toArray(new String[prefsCache.size()]);
D
duke 已提交
643 644 645
    }

    protected String[] childrenNamesSpi() {
646 647 648
        return AccessController.doPrivileged(
            new PrivilegedAction<String[]>() {
                public String[] run() {
649
                    List<String> result = new ArrayList<>();
D
duke 已提交
650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
                    File[] dirContents = dir.listFiles();
                    if (dirContents != null) {
                        for (int i = 0; i < dirContents.length; i++)
                            if (dirContents[i].isDirectory())
                                result.add(nodeName(dirContents[i].getName()));
                    }
                    return result.toArray(EMPTY_STRING_ARRAY);
               }
            });
    }

    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    protected AbstractPreferences childSpi(String name) {
        return new FileSystemPreferences(this, name);
    }

    public void removeNode() throws BackingStoreException {
        synchronized (isUserNode()? userLockFile: systemLockFile) {
            // to remove a node we need an exclusive lock
            if (!lockFile(false))
                throw(new BackingStoreException("Couldn't get file lock."));
           try {
                super.removeNode();
           } finally {
                unlockFile();
           }
        }
    }

    /**
     * Called with file lock held (in addition to node locks).
     */
    protected void removeNodeSpi() throws BackingStoreException {
        try {
685 686 687
            AccessController.doPrivileged(
                new PrivilegedExceptionAction<Void>() {
                public Void run() throws BackingStoreException {
D
duke 已提交
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
                    if (changeLog.contains(nodeCreate)) {
                        changeLog.remove(nodeCreate);
                        nodeCreate = null;
                        return null;
                    }
                    if (!dir.exists())
                        return null;
                    prefsFile.delete();
                    tmpFile.delete();
                    // dir should be empty now.  If it's not, empty it
                    File[] junk = dir.listFiles();
                    if (junk.length != 0) {
                        getLogger().warning(
                           "Found extraneous files when removing node: "
                            + Arrays.asList(junk));
                        for (int i=0; i<junk.length; i++)
                            junk[i].delete();
                    }
                    if (!dir.delete())
                        throw new BackingStoreException("Couldn't delete dir: "
                                                                         + dir);
                    return null;
                }
            });
        } catch (PrivilegedActionException e) {
            throw (BackingStoreException) e.getException();
        }
    }

    public synchronized void sync() throws BackingStoreException {
        boolean userNode = isUserNode();
        boolean shared;

        if (userNode) {
            shared = false; /* use exclusive lock for user prefs */
        } else {
            /* if can write to system root, use exclusive lock.
               otherwise use shared lock. */
            shared = !isSystemRootWritable;
        }
        synchronized (isUserNode()? userLockFile:systemLockFile) {
           if (!lockFile(shared))
               throw(new BackingStoreException("Couldn't get file lock."));
           final Long newModTime =
732 733 734
                AccessController.doPrivileged(
                    new PrivilegedAction<Long>() {
               public Long run() {
D
duke 已提交
735 736 737 738 739 740 741 742 743 744 745 746 747
                   long nmt;
                   if (isUserNode()) {
                       nmt = userRootModFile.lastModified();
                       isUserRootModified = userRootModTime == nmt;
                   } else {
                       nmt = systemRootModFile.lastModified();
                       isSystemRootModified = systemRootModTime == nmt;
                   }
                   return new Long(nmt);
               }
           });
           try {
               super.sync();
748 749
               AccessController.doPrivileged(new PrivilegedAction<Void>() {
                   public Void run() {
D
duke 已提交
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
                   if (isUserNode()) {
                       userRootModTime = newModTime.longValue() + 1000;
                       userRootModFile.setLastModified(userRootModTime);
                   } else {
                       systemRootModTime = newModTime.longValue() + 1000;
                       systemRootModFile.setLastModified(systemRootModTime);
                   }
                   return null;
                   }
               });
           } finally {
                unlockFile();
           }
        }
    }

    protected void syncSpi() throws BackingStoreException {
        try {
768 769 770
            AccessController.doPrivileged(
                new PrivilegedExceptionAction<Void>() {
                public Void run() throws BackingStoreException {
D
duke 已提交
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
                    syncSpiPrivileged();
                    return null;
                }
            });
        } catch (PrivilegedActionException e) {
            throw (BackingStoreException) e.getException();
        }
    }
    private void syncSpiPrivileged() throws BackingStoreException {
        if (isRemoved())
            throw new IllegalStateException("Node has been removed");
        if (prefsCache == null)
            return;  // We've never been used, don't bother syncing
        long lastModifiedTime;
        if ((isUserNode() ? isUserRootModified : isSystemRootModified)) {
            lastModifiedTime = prefsFile.lastModified();
            if (lastModifiedTime  != lastSyncTime) {
                // Prefs at this node were externally modified; read in node and
                // playback any local mods since last sync
                loadCache();
                replayChanges();
                lastSyncTime = lastModifiedTime;
            }
        } else if (lastSyncTime != 0 && !dir.exists()) {
            // This node was removed in the background.  Playback any changes
            // against a virgin (empty) Map.
797
            prefsCache = new TreeMap<>();
D
duke 已提交
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006
            replayChanges();
        }
        if (!changeLog.isEmpty()) {
            writeBackCache();  // Creates directory & file if necessary
           /*
            * Attempt succeeded; it's barely possible that the call to
            * lastModified might fail (i.e., return 0), but this would not
            * be a disaster, as lastSyncTime is allowed to lag.
            */
            lastModifiedTime = prefsFile.lastModified();
            /* If lastSyncTime did not change, or went back
             * increment by 1 second. Since we hold the lock
             * lastSyncTime always monotonically encreases in the
             * atomic sense.
             */
            if (lastSyncTime <= lastModifiedTime) {
                lastSyncTime = lastModifiedTime + 1000;
                prefsFile.setLastModified(lastSyncTime);
            }
            changeLog.clear();
        }
    }

    public void flush() throws BackingStoreException {
        if (isRemoved())
            return;
        sync();
    }

    protected void flushSpi() throws BackingStoreException {
        // assert false;
    }

    /**
     * Returns true if the specified character is appropriate for use in
     * Unix directory names.  A character is appropriate if it's a printable
     * ASCII character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f),
     * dot ('.', 0x2e), or underscore ('_', 0x5f).
     */
    private static boolean isDirChar(char ch) {
        return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_';
    }

    /**
     * Returns the directory name corresponding to the specified node name.
     * Generally, this is just the node name.  If the node name includes
     * inappropriate characters (as per isDirChar) it is translated to Base64.
     * with the underscore  character ('_', 0x5f) prepended.
     */
    private static String dirName(String nodeName) {
        for (int i=0, n=nodeName.length(); i < n; i++)
            if (!isDirChar(nodeName.charAt(i)))
                return "_" + Base64.byteArrayToAltBase64(byteArray(nodeName));
        return nodeName;
    }

    /**
     * Translate a string into a byte array by translating each character
     * into two bytes, high-byte first ("big-endian").
     */
    private static byte[] byteArray(String s) {
        int len = s.length();
        byte[] result = new byte[2*len];
        for (int i=0, j=0; i<len; i++) {
            char c = s.charAt(i);
            result[j++] = (byte) (c>>8);
            result[j++] = (byte) c;
        }
        return result;
    }

    /**
     * Returns the node name corresponding to the specified directory name.
 * (Inverts the transformation of dirName(String).
     */
    private static String nodeName(String dirName) {
        if (dirName.charAt(0) != '_')
            return dirName;
        byte a[] = Base64.altBase64ToByteArray(dirName.substring(1));
        StringBuffer result = new StringBuffer(a.length/2);
        for (int i = 0; i < a.length; ) {
            int highByte = a[i++] & 0xff;
            int lowByte =  a[i++] & 0xff;
            result.append((char) ((highByte << 8) | lowByte));
        }
        return result.toString();
    }

    /**
     * Try to acquire the appropriate file lock (user or system).  If
     * the initial attempt fails, several more attempts are made using
     * an exponential backoff strategy.  If all attempts fail, this method
     * returns false.
     * @throws SecurityException if file access denied.
     */
    private boolean lockFile(boolean shared) throws SecurityException{
        boolean usernode = isUserNode();
        int[] result;
        int errorCode = 0;
        File lockFile = (usernode ? userLockFile : systemLockFile);
        long sleepTime = INIT_SLEEP_TIME;
        for (int i = 0; i < MAX_ATTEMPTS; i++) {
            try {
                  int perm = (usernode? USER_READ_WRITE: USER_RW_ALL_READ);
                  result = lockFile0(lockFile.getCanonicalPath(), perm, shared);

                  errorCode = result[ERROR_CODE];
                  if (result[LOCK_HANDLE] != 0) {
                     if (usernode) {
                         userRootLockHandle = result[LOCK_HANDLE];
                     } else {
                         systemRootLockHandle = result[LOCK_HANDLE];
                     }
                     return true;
                  }
            } catch(IOException e) {
//                // If at first, you don't succeed...
            }

            try {
                Thread.sleep(sleepTime);
            } catch(InterruptedException e) {
                checkLockFile0ErrorCode(errorCode);
                return false;
            }
            sleepTime *= 2;
        }
        checkLockFile0ErrorCode(errorCode);
        return false;
    }

    /**
     * Checks if unlockFile0() returned an error. Throws a SecurityException,
     * if access denied. Logs a warning otherwise.
     */
    private void checkLockFile0ErrorCode (int errorCode)
                                                      throws SecurityException {
        if (errorCode == EACCES)
            throw new SecurityException("Could not lock " +
            (isUserNode()? "User prefs." : "System prefs.") +
             " Lock file access denied.");
        if (errorCode != EAGAIN)
            getLogger().warning("Could not lock " +
                             (isUserNode()? "User prefs. " : "System prefs.") +
                             " Unix error code " + errorCode + ".");
    }

    /**
     * Locks file using UNIX file locking.
     * @param fileName Absolute file name of the lock file.
     * @return Returns a lock handle, used to unlock the file.
     */
    private static native int[]
            lockFile0(String fileName, int permission, boolean shared);

    /**
     * Unlocks file previously locked by lockFile0().
     * @param lockHandle Handle to the file lock.
     * @return Returns zero if OK, UNIX error code if failure.
     */
    private  static native int unlockFile0(int lockHandle);

    /**
     * Changes UNIX file permissions.
     */
    private static native int chmod(String fileName, int permission);

    /**
     * Initial time between lock attempts, in ms.  The time is doubled
     * after each failing attempt (except the first).
     */
    private static int INIT_SLEEP_TIME = 50;

    /**
     * Maximum number of lock attempts.
     */
    private static int MAX_ATTEMPTS = 5;

    /**
     * Release the the appropriate file lock (user or system).
     * @throws SecurityException if file access denied.
     */
    private void unlockFile() {
        int result;
        boolean usernode = isUserNode();
        File lockFile = (usernode ? userLockFile : systemLockFile);
        int lockHandle = ( usernode ? userRootLockHandle:systemRootLockHandle);
        if (lockHandle == 0) {
            getLogger().warning("Unlock: zero lockHandle for " +
                           (usernode ? "user":"system") + " preferences.)");
            return;
        }
        result = unlockFile0(lockHandle);
        if (result != 0) {
            getLogger().warning("Could not drop file-lock on " +
            (isUserNode() ? "user" : "system") + " preferences." +
            " Unix error code " + result + ".");
            if (result == EACCES)
                throw new SecurityException("Could not unlock" +
                (isUserNode()? "User prefs." : "System prefs.") +
                " Lock file access denied.");
        }
        if (isUserNode()) {
            userRootLockHandle = 0;
        } else {
            systemRootLockHandle = 0;
        }
    }
}