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