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