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