1
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
33 class FileSystemPreferences extends AbstractPreferences {
34
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
51 private static Logger getLogger() {
52 return Logger.getLogger("java.util.prefs");
53 }
54
55
58 private static File systemRootDir;
59
60
63 private static boolean isSystemRootWritable;
64
65
68 private static File userRootDir;
69
70
73 private static boolean isUserRootWritable;
74
75
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 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 userRootModFile.createNewFile();
118 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
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 if (!systemRootDir.exists()) {
158 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 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
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
218 static File userLockFile;
219
220
221
222
225 static File systemLockFile;
226
227
231
232 private static int userRootLockHandle = 0;
233
234
238
239 private static int systemRootLockHandle = 0;
240
241
248 private final File dir;
249
250
256 private final File prefsFile;
257
258
265 private final File tmpFile;
266
267
270 private static File userRootModFile;
271
272
275 private static boolean isUserRootModified = false;
276
277
282 private static long userRootModTime;
283
284
285
288 private static File systemRootModFile;
289
292 private static boolean isSystemRootModified = false;
293
294
299 private static long systemRootModTime;
300
301
308 private Map prefsCache = null;
309
310
319 private long lastSyncTime = 0;
320
321
324 private static final int EAGAIN = 11;
325
326
329 private static final int EACCES = 13;
330
331
332 private static final int LOCK_HANDLE = 0;
333 private static final int ERROR_CODE = 1;
334
335
344 final List changeLog = new ArrayList();
345
346
349 private abstract class Change {
350
353 abstract void replay();
354 };
355
356
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
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
390 private class NodeCreate extends Change {
391
396 void replay() {
397 }
398 }
399
400
403 NodeCreate nodeCreate = null;
404
405
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);
415 static {
416 syncTimer.schedule(new TimerTask() {
418 public void run() {
419 syncWorld();
420 }
421 }, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000);
422
423 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
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
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
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 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
532 private void initCacheIfNecessary() {
533 if (prefsCache != null)
534 return;
535
536 try {
537 loadCache();
538 } catch(Exception e) {
539 prefsCache = new TreeMap();
541 }
542 }
543
544
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 prefsCache = m;
580 lastSyncTime = newLastSyncTime;
581 return null;
582 }
583 });
584 } catch (PrivilegedActionException e) {
585 throw (BackingStoreException) e.getException();
586 }
587 }
588
589
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 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
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 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;
708 } else {
709
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; long lastModifiedTime;
768 if ((isUserNode() ? isUserRootModified : isSystemRootModified)) {
769 lastModifiedTime = prefsFile.lastModified();
770 if (lastModifiedTime != lastSyncTime) {
771 loadCache();
774 replayChanges();
775 lastSyncTime = lastModifiedTime;
776 }
777 } else if (lastSyncTime != 0 && !dir.exists()) {
778 prefsCache = new TreeMap();
781 replayChanges();
782 }
783 if (!changeLog.isEmpty()) {
784 writeBackCache();
790 lastModifiedTime = prefsFile.lastModified();
791
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 }
813
814
820 private static boolean isDirChar(char ch) {
821 return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_';
822 }
823
824
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
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
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
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 }
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
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
933 private static native int[]
934 lockFile0(String fileName, int permission, boolean shared);
935
936
941 private static native int unlockFile0(int lockHandle);
942
943
946 private static native int chmod(String fileName, int permission);
947
948
952 private static int INIT_SLEEP_TIME = 50;
953
954
957 private static int MAX_ATTEMPTS = 5;
958
959
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