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