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