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