• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

WindhoverLabs / phoebus / #69

26 Aug 2023 11:53PM UTC coverage: 16.527% (-0.02%) from 16.543%
#69

push

lorenzo-gomez-windhover
-CSV Exporter functional

17716 of 107197 relevant lines covered (16.53%)

0.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

37.39
/core/framework/src/main/java/org/phoebus/framework/preferences/FileSystemPreferences.java
1
/*
2
 * Copyright 2000-2006 Sun Microsystems, Inc.  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.  Sun designates this
8
 * particular file as subject to the "Classpath" exception as provided
9
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22
 * CA 95054 USA or visit www.sun.com if you need additional information or
23
 * have any questions.
24
 */
25

26
package org.phoebus.framework.preferences;
27

28
import java.io.File;
29
import java.io.FileInputStream;
30
import java.io.FileNotFoundException;
31
import java.io.FileOutputStream;
32
import java.io.IOException;
33
import java.io.RandomAccessFile;
34
import java.nio.channels.FileLock;
35
import java.nio.file.Files;
36
import java.nio.file.StandardCopyOption;
37
import java.security.AccessController;
38
import java.security.PrivilegedAction;
39
import java.security.PrivilegedActionException;
40
import java.security.PrivilegedExceptionAction;
41
import java.util.ArrayList;
42
import java.util.Arrays;
43
import java.util.List;
44
import java.util.Map;
45
import java.util.Timer;
46
import java.util.TimerTask;
47
import java.util.TreeMap;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import java.util.prefs.AbstractPreferences;
51
import java.util.prefs.BackingStoreException;
52
import java.util.prefs.InvalidPreferencesFormatException;
53
import java.util.prefs.Preferences;
54

55
import org.phoebus.framework.workbench.Locations;
56

57
/**
58
 * Preferences implementation for Unix. Preferences are stored in the file
59
 * system, with one directory per preferences node. All of the preferences at
60
 * each node are stored in a single file. Atomic file system operations (e.g.
61
 * File.renameTo) are used to ensure integrity. An in-memory cache of the
62
 * "explored" portion of the tree is maintained for performance, and written
63
 * back to the disk periodically. File-locking is used to ensure reasonable
64
 * behavior when multiple VMs are running at the same time. (The file lock is
65
 * obtained only for sync(), flush() and removeNode().)
66
 *
67
 * @author Josh Bloch
68
 * @see Preferences
69
 * @since 1.4
70
 */
71

72
@SuppressWarnings({"unchecked", "nls"})
73
class FileSystemPreferences extends AbstractPreferences {
74

75
    /**
76
     * Sync interval in seconds.
77
     */
78
    private static final int SYNC_INTERVAL = Math.max(1,
1✔
79
            Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction<String>() {
1✔
80
                @Override
81
                public String run() {
82
                    return System.getProperty("java.util.prefs.syncInterval", "30");
1✔
83
                }
84
            })));
85

86
    /**
87
     * Returns logger for error messages. Backing store exceptions are logged at
88
     * WARNING level.
89
     */
90
    private static Logger getLogger() {
91
        return Logger.getLogger("java.util.prefs");
1✔
92
    }
93

94
    /**
95
     * Directory for system preferences.
96
     */
97
    private static File systemRootDir;
98

99
    /*
100
     * Flag, indicating whether systemRoot directory is writable
101
     */
102
    private static boolean isSystemRootWritable;
103

104
    /**
105
     * Directory for user preferences.
106
     */
107
    private static File userRootDir;
108

109
    /*
110
     * Flag, indicating whether userRoot directory is writable
111
     */
112
    private static boolean isUserRootWritable;
113

114
    /**
115
     * The user root.
116
     */
117
    static Preferences userRoot = null;
1✔
118

119
    static synchronized Preferences getUserRoot() {
120
        if (userRoot == null) {
1✔
121
            setupUserRoot();
1✔
122
            userRoot = new FileSystemPreferences(true);
1✔
123
        }
124
        return userRoot;
1✔
125
    }
126

127
    private static void setupUserRoot() {
128
        AccessController.doPrivileged(new PrivilegedAction() {
1✔
129
            @Override
130
            public Object run() {
131

132
                // If Phoebus locations have been set,
133
                // place the user preferences in the user directory that also stores mementos etc.
134
                // Otherwise fall back to original FileSystemPreferences behavior
135
                if (System.getProperty(Locations.PHOEBUS_USER, "").length() > 0)
1✔
136
                    userRootDir = new File(System.getProperty(Locations.PHOEBUS_USER), ".userPrefs");
×
137
                else
138
                    userRootDir = new File(System.getProperty("java.util.prefs.userRoot", System.getProperty("user.home")),
1✔
139
                        ".phoebus/.userPrefs");
140

141
                // Attempt to create root dir if it does not yet exist.
142
                if (!userRootDir.exists()) {
1✔
143
                    if (userRootDir.mkdirs()) {
1✔
144
                        getLogger().info("Created user preferences directory.");
1✔
145
                    } else
146
                        getLogger().warning(
×
147
                                "Couldn't create user preferences" + " directory. User preferences are unusable.");
148
                }
149
                isUserRootWritable = userRootDir.canWrite();
1✔
150
                String USER_NAME = System.getProperty("user.name");
1✔
151
                userLockFile = new File(userRootDir, ".user.lock." + USER_NAME);
1✔
152
                userRootModFile = new File(userRootDir, ".userRootModFile." + USER_NAME);
1✔
153
                if (!userRootModFile.exists())
1✔
154
                    try {
155
                        // create if does not exist.
156
                        userRootModFile.createNewFile();
1✔
157
                    } catch (IOException e) {
×
158
                        getLogger().warning(e.toString());
×
159
                    }
1✔
160
                userRootModTime = userRootModFile.lastModified();
1✔
161
                return null;
1✔
162
            }
163
        });
164
    }
1✔
165

166
    /**
167
     * The system root.
168
     */
169
    static Preferences systemRoot;
170

171
    static synchronized Preferences getSystemRoot() {
172
        if (systemRoot == null) {
×
173
            setupSystemRoot();
×
174
            systemRoot = new FileSystemPreferences(false);
×
175
        }
176
        return systemRoot;
×
177
    }
178

179
    private static void setupSystemRoot() {
180
        AccessController.doPrivileged(new PrivilegedAction() {
×
181
            @Override
182
            public Object run() {
183
                String systemPrefsDirName = System.getProperty("java.util.prefs.systemRoot", "/etc/.java");
×
184
                systemRootDir = new File(systemPrefsDirName, ".systemPrefs");
×
185
                // Attempt to create root dir if it does not yet exist.
186
                if (!systemRootDir.exists()) {
×
187
                    // system root does not exist in /etc/.java
188
                    // Switching to java.home
189
                    systemRootDir = new File(System.getProperty("java.home"), ".systemPrefs");
×
190
                    if (!systemRootDir.exists()) {
×
191
                        if (systemRootDir.mkdirs()) {
×
192
                            getLogger().info("Created system preferences directory " + "in java.home.");
×
193
                        } else {
194
                            getLogger().warning("Could not create " + "system preferences directory. System "
×
195
                                    + "preferences are unusable.");
196
                        }
197
                    }
198
                }
199
                isSystemRootWritable = systemRootDir.canWrite();
×
200
                systemLockFile = new File(systemRootDir, ".system.lock");
×
201
                systemRootModFile = new File(systemRootDir, ".systemRootModFile");
×
202
                if (!systemRootModFile.exists() && isSystemRootWritable)
×
203
                    try {
204
                        // create if does not exist.
205
                        systemRootModFile.createNewFile();
×
206
                    } catch (IOException e) {
×
207
                        getLogger().warning(e.toString());
×
208
                    }
×
209
                systemRootModTime = systemRootModFile.lastModified();
×
210
                return null;
×
211
            }
212
        });
213
    }
×
214

215
    /**
216
     * The lock file for the user tree.
217
     */
218
    static File userLockFile;
219

220
    /**
221
     * The lock file for the system tree.
222
     */
223
    static File systemLockFile;
224

225
    /**
226
     * Unix lock handle for userRoot. Zero, if unlocked.
227
     */
228

229
    private static RandomAccessFile userRootLockFile = null;
1✔
230
    private static FileLock userRootLockHandle = null;
1✔
231

232
    /**
233
     * Unix lock handle for systemRoot. Zero, if unlocked.
234
     */
235

236
    private static RandomAccessFile systemRootLockFile = null;
1✔
237
    private static FileLock systemRootLockHandle = null;
1✔
238

239
    /**
240
     * The directory representing this preference node. There is no guarantee that
241
     * this directory exits, as another VM can delete it at any time that it (the
242
     * other VM) holds the file-lock. While the root node cannot be deleted, it may
243
     * not yet have been created, or the underlying directory could have been
244
     * deleted accidentally.
245
     */
246
    private final File dir;
247

248
    /**
249
     * The file representing this preference node's preferences. The file format is
250
     * undocumented, and subject to change from release to release, but I'm sure
251
     * that you can figure it out if you try real hard.
252
     */
253
    private final File prefsFile;
254

255
    /**
256
     * A temporary file used for saving changes to preferences. As part of the sync
257
     * operation, changes are first saved into this file, and then atomically
258
     * renamed to prefsFile. This results in an atomic state change from one valid
259
     * set of preferences to another. The the file-lock is held for the duration of
260
     * this transformation.
261
     */
262
    private final File tmpFile;
263

264
    /**
265
     * File, which keeps track of global modifications of userRoot.
266
     */
267
    private static File userRootModFile;
268

269
    /**
270
     * Flag, which indicated whether userRoot was modified by another VM
271
     */
272
    private static boolean isUserRootModified = false;
1✔
273

274
    /**
275
     * Keeps track of userRoot modification time. This time is reset to zero after
276
     * UNIX reboot, and is increased by 1 second each time userRoot is modified.
277
     */
278
    private static long userRootModTime;
279

280
    /*
281
     * File, which keeps track of global modifications of systemRoot
282
     */
283
    private static File systemRootModFile;
284
    /*
285
     * Flag, which indicates whether systemRoot was modified by another VM
286
     */
287
    private static boolean isSystemRootModified = false;
1✔
288

289
    /**
290
     * Keeps track of systemRoot modification time. This time is reset to zero after
291
     * system reboot, and is increased by 1 second each time systemRoot is modified.
292
     */
293
    private static long systemRootModTime;
294

295
    /**
296
     * Locally cached preferences for this node (includes uncommitted changes). This
297
     * map is initialized with from disk when the first get or put operation occurs
298
     * on this node. It is synchronized with the corresponding disk file (prefsFile)
299
     * by the sync operation. The initial value is read *without* acquiring the
300
     * file-lock.
301
     */
302
    private Map prefsCache = null;
1✔
303

304
    /**
305
     * The last modification time of the file backing this node at the time that
306
     * prefCache was last synchronized (or initially read). This value is set
307
     * *before* reading the file, so it's conservative; the actual timestamp could
308
     * be (slightly) higher. A value of zero indicates that we were unable to
309
     * initialize prefsCache from the disk, or have not yet attempted to do so. (If
310
     * prefsCache is non-null, it indicates the former; if it's null, the latter.)
311
     */
312
    private long lastSyncTime = 0;
1✔
313

314
    /**
315
     * Unix error code for locked file.
316
     */
317
    private static final int EAGAIN = 11;
318

319
    /**
320
     * Unix error code for denied access.
321
     */
322
    private static final int EACCES = 13;
323

324
    /**
325
     * A list of all uncommitted preference changes. The elements in this list are
326
     * of type PrefChange. If this node is concurrently modified on disk by another
327
     * VM, the two sets of changes are merged when this node is sync'ed by
328
     * overwriting our prefsCache with the preference map last written out to disk
329
     * (by the other VM), and then replaying this change log against that map. The
330
     * resulting map is then written back to the disk.
331
     */
332
    final List changeLog = new ArrayList();
1✔
333

334
    /**
335
     * Represents a change to a preference.
336
     */
337
    private abstract class Change {
1✔
338
        /**
339
         * Reapplies the change to prefsCache.
340
         */
341
        abstract void replay();
342
    };
343

344
    /**
345
     * Represents a preference put.
346
     */
347
    private class Put extends Change {
348
        String key, value;
349

350
        Put(String key, String value) {
×
351
            this.key = key;
×
352
            this.value = value;
×
353
        }
×
354

355
        @Override
356
        void replay() {
357
            prefsCache.put(key, value);
×
358
        }
×
359
    }
360

361
    /**
362
     * Represents a preference remove.
363
     */
364
    private class Remove extends Change {
365
        String key;
366

367
        Remove(String key) {
×
368
            this.key = key;
×
369
        }
×
370

371
        @Override
372
        void replay() {
373
            prefsCache.remove(key);
×
374
        }
×
375
    }
376

377
    /**
378
     * Represents the creation of this node.
379
     */
380
    private class NodeCreate extends Change {
1✔
381
        /**
382
         * Performs no action, but the presence of this object in changeLog will force
383
         * the node and its ancestors to be made permanent at the next sync.
384
         */
385
        @Override
386
        void replay() {
387
        }
×
388
    }
389

390
    /**
391
     * NodeCreate object for this node.
392
     */
393
    NodeCreate nodeCreate = null;
1✔
394

395
    /**
396
     * Replay changeLog against prefsCache.
397
     */
398
    private void replayChanges() {
399
        for (int i = 0, n = changeLog.size(); i < n; i++)
×
400
            ((Change) changeLog.get(i)).replay();
×
401
    }
×
402

403
    private static Timer syncTimer = new Timer(true); // Daemon Thread
1✔
404

405
    static {
406
        // Add periodic timer task to periodically sync cached prefs
407
        syncTimer.schedule(new TimerTask() {
1✔
408
            @Override
409
            public void run() {
410
                syncWorld();
×
411
            }
×
412
        }, SYNC_INTERVAL * 1000, SYNC_INTERVAL * 1000);
413

414
        // Add shutdown hook to flush cached prefs on normal termination
415
        AccessController.doPrivileged(new PrivilegedAction() {
1✔
416
            @Override
417
            public Object run() {
418
                Runtime.getRuntime().addShutdownHook(new Thread() {
1✔
419
                    @Override
420
                    public void run() {
421
                        syncTimer.cancel();
1✔
422
                        syncWorld();
×
423
                    }
×
424
                });
425
                return null;
1✔
426
            }
427
        });
428
    }
429

430
    private static void syncWorld() {
431
        /*
432
         * Synchronization necessary because userRoot and systemRoot are lazily
433
         * initialized.
434
         */
435
        Preferences userRt;
436
        Preferences systemRt;
437
        synchronized (FileSystemPreferences.class) {
1✔
438
            userRt = userRoot;
1✔
439
            systemRt = systemRoot;
1✔
440
        }
1✔
441

442
        try {
443
            if (userRt != null)
1✔
444
                userRt.flush();
×
445
        } catch (BackingStoreException e) {
×
446
            getLogger().warning("Couldn't flush user prefs: " + e);
×
447
        }
×
448

449
        try {
450
            if (systemRt != null)
×
451
                systemRt.flush();
×
452
        } catch (BackingStoreException e) {
×
453
            getLogger().warning("Couldn't flush system prefs: " + e);
×
454
        }
×
455
    }
×
456

457
    private final boolean isUserNode;
458

459
    /**
460
     * Special constructor for roots (both user and system). This constructor will
461
     * only be called twice, by the static initializer.
462
     */
463
    private FileSystemPreferences(boolean user) {
464
        super(null, "");
1✔
465
        isUserNode = user;
1✔
466
        dir = (user ? userRootDir : systemRootDir);
1✔
467
        prefsFile = new File(dir, "prefs.xml");
1✔
468
        tmpFile = new File(dir, "prefs.tmp");
1✔
469
    }
1✔
470

471
    /**
472
     * Construct a new FileSystemPreferences instance with the specified parent node
473
     * and name. This constructor, called from childSpi, is used to make every node
474
     * except for the two //roots.
475
     */
476
    private FileSystemPreferences(FileSystemPreferences parent, String name) {
477
        super(parent, name);
1✔
478
        isUserNode = parent.isUserNode;
1✔
479
        dir = new File(parent.dir, dirName(name));
1✔
480
        prefsFile = new File(dir, "prefs.xml");
1✔
481
        tmpFile = new File(dir, "prefs.tmp");
1✔
482
        AccessController.doPrivileged(new PrivilegedAction() {
1✔
483
            @Override
484
            public Object run() {
485
                newNode = !dir.exists();
1✔
486
                return null;
1✔
487
            }
488
        });
489
        if (newNode) {
1✔
490
            // These 2 things guarantee node will get wrtten at next flush/sync
491
            prefsCache = new TreeMap();
1✔
492
            nodeCreate = new NodeCreate();
1✔
493
            changeLog.add(nodeCreate);
1✔
494
        }
495
    }
1✔
496

497
    @Override
498
    public boolean isUserNode() {
499
        return isUserNode;
1✔
500
    }
501

502
    @Override
503
    protected void putSpi(String key, String value) {
504
        initCacheIfNecessary();
×
505
        changeLog.add(new Put(key, value));
×
506
        prefsCache.put(key, value);
×
507
    }
×
508

509
    @Override
510
    protected String getSpi(String key) {
511
        initCacheIfNecessary();
1✔
512
        return (String) prefsCache.get(key);
1✔
513
    }
514

515
    @Override
516
    protected void removeSpi(String key) {
517
        initCacheIfNecessary();
×
518
        changeLog.add(new Remove(key));
×
519
        prefsCache.remove(key);
×
520
    }
×
521

522
    /**
523
     * Initialize prefsCache if it has yet to be initialized. When this method
524
     * returns, prefsCache will be non-null. If the data was successfully read from
525
     * the file, lastSyncTime will be updated. If prefsCache was null, but it was
526
     * impossible to read the file (because it didn't exist or for any other reason)
527
     * prefsCache will be initialized to an empty, modifiable Map, and lastSyncTime
528
     * remain zero.
529
     */
530
    private void initCacheIfNecessary() {
531
        if (prefsCache != null)
1✔
532
            return;
1✔
533

534
        try {
535
            loadCache();
1✔
536
        } catch (Exception e) {
×
537
            // assert lastSyncTime == 0;
538
            prefsCache = new TreeMap();
×
539
        }
1✔
540
    }
1✔
541

542
    /**
543
     * Attempt to load prefsCache from the backing store. If the attempt succeeds,
544
     * lastSyncTime will be updated (the new value will typically correspond to the
545
     * data loaded into the map, but it may be less, if another VM is updating this
546
     * node concurrently). If the attempt fails, a BackingStoreException is thrown
547
     * and both prefsCache and lastSyncTime are unaffected by the call.
548
     */
549
    private void loadCache() throws BackingStoreException {
550
        try {
551
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
1✔
552
                @Override
553
                public Object run() throws BackingStoreException {
554
                    Map m = new TreeMap();
1✔
555
                    long newLastSyncTime = 0;
1✔
556
                    try {
557
                        newLastSyncTime = prefsFile.lastModified();
1✔
558
                        FileInputStream fis = new FileInputStream(prefsFile);
1✔
559
                        FilePreferencesXmlSupport.importMap(fis, m);
1✔
560
                        fis.close();
1✔
561
                    } catch (Exception e) {
1✔
562
                        if (e instanceof InvalidPreferencesFormatException) {
1✔
563
                            getLogger().warning("Invalid preferences format in " + prefsFile.getPath());
×
564
                            prefsFile.renameTo(new File(prefsFile.getParentFile(), "IncorrectFormatPrefs.xml"));
×
565
                            m = new TreeMap();
×
566
                        } else if (e instanceof FileNotFoundException) {
1✔
567
                            getLogger().warning("Prefs file removed in background " + prefsFile.getPath());
1✔
568
                        } else {
569
                            throw new BackingStoreException(e);
×
570
                        }
571
                    }
1✔
572
                    // Attempt succeeded; update state
573
                    prefsCache = m;
1✔
574
                    lastSyncTime = newLastSyncTime;
1✔
575
                    return null;
1✔
576
                }
577
            });
578
        } catch (PrivilegedActionException e) {
×
579
            throw (BackingStoreException) e.getException();
×
580
        }
1✔
581
    }
1✔
582

583
    /**
584
     * Attempt to write back prefsCache to the backing store. If the attempt
585
     * succeeds, lastSyncTime will be updated (the new value will correspond exactly
586
     * to the data thust written back, as we hold the file lock, which prevents a
587
     * concurrent write. If the attempt fails, a BackingStoreException is thrown and
588
     * both the backing store (prefsFile) and lastSyncTime will be unaffected by
589
     * this call. This call will NEVER leave prefsFile in a corrupt state.
590
     */
591
    private void writeBackCache() throws BackingStoreException {
592
        try {
593
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
×
594
                @Override
595
                public Object run() throws BackingStoreException {
596
                    try {
597
                        if (!dir.exists() && !dir.mkdirs())
×
598
                            throw new BackingStoreException(dir + " create failed.");
×
599
                        FileOutputStream fos = new FileOutputStream(tmpFile);
×
600
                        FilePreferencesXmlSupport.exportMap(fos, prefsCache);
×
601
                        fos.close();
×
602
                        Files.move(tmpFile.toPath(), prefsFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
×
603
                    } catch (Exception e) {
×
604
                        throw new BackingStoreException(e);
×
605
                    }
×
606
                    return null;
×
607
                }
608
            });
609
        } catch (PrivilegedActionException e) {
×
610
            throw (BackingStoreException) e.getException();
×
611
        }
×
612
    }
×
613

614
    @Override
615
    protected String[] keysSpi() {
616
        initCacheIfNecessary();
1✔
617
        return (String[]) prefsCache.keySet().toArray(new String[prefsCache.size()]);
1✔
618
    }
619

620
    @Override
621
    protected String[] childrenNamesSpi() {
622
        return (String[]) AccessController.doPrivileged(new PrivilegedAction() {
1✔
623
            @Override
624
            public Object run() {
625
                List result = new ArrayList();
1✔
626
                File[] dirContents = dir.listFiles();
1✔
627
                if (dirContents != null) {
1✔
628
                    for (int i = 0; i < dirContents.length; i++)
1✔
629
                        if (dirContents[i].isDirectory())
1✔
630
                            result.add(nodeName(dirContents[i].getName()));
1✔
631
                }
632
                return result.toArray(EMPTY_STRING_ARRAY);
1✔
633
            }
634
        });
635
    }
636

637
    private static final String[] EMPTY_STRING_ARRAY = new String[0];
1✔
638

639
    @Override
640
    protected AbstractPreferences childSpi(String name) {
641
        return new FileSystemPreferences(this, name);
1✔
642
    }
643

644
    @Override
645
    public void removeNode() throws BackingStoreException {
646
        synchronized (isUserNode() ? userLockFile : systemLockFile) {
×
647
            // to remove a node we need an exclusive lock
648
            if (!lockFile(false))
×
649
                throw (new BackingStoreException("Couldn't get file lock."));
×
650
            try {
651
                super.removeNode();
×
652
            } finally {
653
                unlockFile();
×
654
            }
655
        }
×
656
    }
×
657

658
    /**
659
     * Called with file lock held (in addition to node locks).
660
     */
661
    @Override
662
    protected void removeNodeSpi() throws BackingStoreException {
663
        try {
664
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
×
665
                @Override
666
                public Object run() throws BackingStoreException {
667
                    if (changeLog.contains(nodeCreate)) {
×
668
                        changeLog.remove(nodeCreate);
×
669
                        nodeCreate = null;
×
670
                        return null;
×
671
                    }
672
                    if (!dir.exists())
×
673
                        return null;
×
674
                    prefsFile.delete();
×
675
                    tmpFile.delete();
×
676
                    // dir should be empty now. If it's not, empty it
677
                    File[] junk = dir.listFiles();
×
678
                    if (junk.length != 0) {
×
679
                        getLogger().warning("Found extraneous files when removing node: " + Arrays.asList(junk));
×
680
                        for (int i = 0; i < junk.length; i++)
×
681
                            junk[i].delete();
×
682
                    }
683
                    if (!dir.delete())
×
684
                        throw new BackingStoreException("Couldn't delete dir: " + dir);
×
685
                    return null;
×
686
                }
687
            });
688
        } catch (PrivilegedActionException e) {
×
689
            throw (BackingStoreException) e.getException();
×
690
        }
×
691
    }
×
692

693
    @Override
694
    public synchronized void sync() throws BackingStoreException {
695
        boolean userNode = isUserNode();
1✔
696
        boolean shared;
697

698
        if (userNode) {
1✔
699
            shared = false; /* use exclusive lock for user prefs */
1✔
700
        } else {
701
            /*
702
             * if can write to system root, use exclusive lock. otherwise use shared lock.
703
             */
704
            shared = !isSystemRootWritable;
×
705
        }
706
        synchronized (isUserNode() ? userLockFile : systemLockFile) {
1✔
707
            if (!lockFile(shared))
×
708
                throw (new BackingStoreException("Couldn't get file lock."));
×
709
            final Long newModTime = (Long) AccessController.doPrivileged(new PrivilegedAction() {
×
710
                @Override
711
                public Object run() {
712
                    long nmt;
713
                    if (isUserNode()) {
×
714
                        nmt = userRootModFile.lastModified();
×
715
                        isUserRootModified = userRootModTime == nmt;
×
716
                    } else {
717
                        nmt = systemRootModFile.lastModified();
×
718
                        isSystemRootModified = systemRootModTime == nmt;
×
719
                    }
720
                    return Long.valueOf(nmt);
×
721
                }
722
            });
723
            try {
724
                super.sync();
×
725
                AccessController.doPrivileged(new PrivilegedAction() {
×
726
                    @Override
727
                    public Object run() {
728
                        if (isUserNode()) {
×
729
                            userRootModTime = newModTime.longValue() + 1000;
×
730
                            userRootModFile.setLastModified(userRootModTime);
×
731
                        } else {
732
                            systemRootModTime = newModTime.longValue() + 1000;
×
733
                            systemRootModFile.setLastModified(systemRootModTime);
×
734
                        }
735
                        return null;
×
736
                    }
737
                });
738
            } finally {
739
                unlockFile();
×
740
            }
741
        }
×
742
    }
×
743

744
    @Override
745
    protected void syncSpi() throws BackingStoreException {
746
        try {
747
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
×
748
                @Override
749
                public Object run() throws BackingStoreException {
750
                    syncSpiPrivileged();
×
751
                    return null;
×
752
                }
753
            });
754
        } catch (PrivilegedActionException e) {
×
755
            throw (BackingStoreException) e.getException();
×
756
        }
×
757
    }
×
758

759
    private void syncSpiPrivileged() throws BackingStoreException {
760
        if (isRemoved())
×
761
            throw new IllegalStateException("Node has been removed");
×
762
        if (prefsCache == null)
×
763
            return; // We've never been used, don't bother syncing
×
764
        long lastModifiedTime;
765
        if ((isUserNode() ? isUserRootModified : isSystemRootModified)) {
×
766
            lastModifiedTime = prefsFile.lastModified();
×
767
            if (lastModifiedTime != lastSyncTime) {
×
768
                // Prefs at this node were externally modified; read in node and
769
                // playback any local mods since last sync
770
                loadCache();
×
771
                replayChanges();
×
772
                lastSyncTime = lastModifiedTime;
×
773
            }
774
        } else if (lastSyncTime != 0 && !dir.exists()) {
×
775
            // This node was removed in the background. Playback any changes
776
            // against a virgin (empty) Map.
777
            prefsCache = new TreeMap();
×
778
            replayChanges();
×
779
        }
780
        if (!changeLog.isEmpty()) {
×
781
            writeBackCache(); // Creates directory & file if necessary
×
782
            /*
783
             * Attempt succeeded; it's barely possible that the call to lastModified might
784
             * fail (i.e., return 0), but this would not be a disaster, as lastSyncTime is
785
             * allowed to lag.
786
             */
787
            lastModifiedTime = prefsFile.lastModified();
×
788
            /*
789
             * If lastSyncTime did not change, or went back increment by 1 second. Since we
790
             * hold the lock lastSyncTime always monotonically encreases in the atomic
791
             * sense.
792
             */
793
            if (lastSyncTime <= lastModifiedTime) {
×
794
                lastSyncTime = lastModifiedTime + 1000;
×
795
                prefsFile.setLastModified(lastSyncTime);
×
796
            }
797
            changeLog.clear();
×
798
        }
799
    }
×
800

801
    @Override
802
    public void flush() throws BackingStoreException {
803
        if (isRemoved())
×
804
            return;
×
805
        sync();
×
806
    }
×
807

808
    @Override
809
    protected void flushSpi() throws BackingStoreException {
810
        // assert false;
811
    }
×
812

813
    @Override
814
    protected boolean isRemoved() {
815
        return super.isRemoved();
1✔
816
    }
817

818
    /**
819
     * Returns true if the specified character is appropriate for use in Unix
820
     * directory names. A character is appropriate if it's a printable ASCII
821
     * character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f), dot ('.',
822
     * 0x2e), or underscore ('_', 0x5f).
823
     */
824
    private static boolean isDirChar(char ch) {
825
        return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_';
1✔
826
    }
827

828
    /**
829
     * Returns the directory name corresponding to the specified node name.
830
     * Generally, this is just the node name. If the node name includes
831
     * inappropriate characters (as per isDirChar) it is translated to Base64. with
832
     * the underscore character ('_', 0x5f) prepended.
833
     */
834
    private static String dirName(String nodeName) {
835
        for (int i = 0, n = nodeName.length(); i < n; i++)
1✔
836
            if (!isDirChar(nodeName.charAt(i)))
1✔
837
                return "_" + Base64.byteArrayToAltBase64(byteArray(nodeName));
×
838
        return nodeName;
1✔
839
    }
840

841
    /**
842
     * Translate a string into a byte array by translating each character into two
843
     * bytes, high-byte first ("big-endian").
844
     */
845
    private static byte[] byteArray(String s) {
846
        int len = s.length();
×
847
        byte[] result = new byte[2 * len];
×
848
        for (int i = 0, j = 0; i < len; i++) {
×
849
            char c = s.charAt(i);
×
850
            result[j++] = (byte) (c >> 8);
×
851
            result[j++] = (byte) c;
×
852
        }
853
        return result;
×
854
    }
855

856
    /**
857
     * Returns the node name corresponding to the specified directory name. (Inverts
858
     * the transformation of dirName(String).
859
     */
860
    private static String nodeName(String dirName) {
861
        if (dirName.charAt(0) != '_')
1✔
862
            return dirName;
1✔
863
        byte a[] = Base64.altBase64ToByteArray(dirName.substring(1));
×
864
        StringBuffer result = new StringBuffer(a.length / 2);
×
865
        for (int i = 0; i < a.length;) {
×
866
            int highByte = a[i++] & 0xff;
×
867
            int lowByte = a[i++] & 0xff;
×
868
            result.append((char) ((highByte << 8) | lowByte));
×
869
        }
×
870
        return result.toString();
×
871
    }
872

873
    /**
874
     * Try to acquire the appropriate file lock (user or system). If the initial
875
     * attempt fails, several more attempts are made using an exponential backoff
876
     * strategy. If all attempts fail, this method returns false.
877
     *
878
     * @throws SecurityException
879
     *             if file access denied.
880
     */
881
    boolean lockFile(boolean shared) throws SecurityException {
882
        boolean usernode = isUserNode();
1✔
883
        int errorCode = 0;
1✔
884
        File lockFile = (usernode ? userLockFile : systemLockFile);
1✔
885
        long sleepTime = INIT_SLEEP_TIME;
1✔
886
        for (int i = 0; i < MAX_ATTEMPTS; i++) {
1✔
887
            try {
888
                RandomAccessFile file = new RandomAccessFile(lockFile.getCanonicalPath(), "rw");
1✔
889
                FileLock lock = file.getChannel().lock();
1✔
890

891
                if (lock != null) {
1✔
892
                    if (usernode) {
1✔
893
                        userRootLockFile = file;
1✔
894
                        userRootLockHandle = lock;
1✔
895
                    } else {
896
                        systemRootLockFile = file;
×
897
                        systemRootLockHandle = lock;
×
898
                    }
899
                    return true;
1✔
900
                }
901
            } catch (IOException e) {
×
902
            }
×
903

904
            try {
905
                Thread.sleep(sleepTime);
×
906
            } catch (InterruptedException e) {
×
907
                checkLockFile0ErrorCode(errorCode);
×
908
                return false;
×
909
            }
×
910
            sleepTime *= 2;
×
911
        }
912
        checkLockFile0ErrorCode(errorCode);
×
913
        return false;
×
914
    }
915

916
    /**
917
     * Checks if unlockFile0() returned an error. Throws a SecurityException, if
918
     * access denied. Logs a warning otherwise.
919
     */
920
    private void checkLockFile0ErrorCode(int errorCode) throws SecurityException {
921
        if (errorCode == EACCES)
×
922
            throw new SecurityException(
×
923
                    "Could not lock " + (isUserNode() ? "User prefs." : "System prefs.") + " Lock file access denied.");
×
924
        if (errorCode != EAGAIN)
×
925
            getLogger().warning("Could not lock " + (isUserNode() ? "User prefs. " : "System prefs.")
×
926
                    + " Unix error code " + errorCode + ".");
927
    }
×
928

929
    /**
930
     * Initial time between lock attempts, in ms. The time is doubled after each
931
     * failing attempt (except the first).
932
     */
933
    private static int INIT_SLEEP_TIME = 50;
1✔
934

935
    /**
936
     * Maximum number of lock attempts.
937
     */
938
    private static int MAX_ATTEMPTS = 5;
1✔
939

940
    /**
941
     * Release the the appropriate file lock (user or system).
942
     *
943
     * @throws SecurityException
944
     *             if file access denied.
945
     */
946
    void unlockFile() {
947
        boolean usernode = isUserNode();
×
948
        RandomAccessFile rootLockFile = (usernode ? userRootLockFile : systemRootLockFile);
×
949
        FileLock lockHandle = (usernode ? userRootLockHandle : systemRootLockHandle);
×
950
        try {
951
            lockHandle.close();
×
952
        } catch (IOException e) {
×
953
            getLogger().warning("Unlock: zero lockHandle for " + (usernode ? "user" : "system") + " preferences.)");
×
954
            return;
×
955
        } finally {
956
            try {
957
                rootLockFile.close();
×
958
            } catch (IOException e) {
×
959
                getLogger().log(Level.WARNING, "Cannot close lock file " + rootLockFile, e);
×
960
            }
×
961
        }
962

963
    }
×
964
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc