KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > util > prefs > FileSystemPreferences


1 /*
2  * @(#)FileSystemPreferences.java 1.20 04/02/16
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN 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 1.20, 02/16/04
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
Popular Tags