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