Path: blob/master/src/java.prefs/unix/classes/java/util/prefs/FileSystemPreferences.java
41139 views
/*1* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.util.prefs;26import java.util.*;27import java.io.*;28import java.security.AccessController;29import java.security.PrivilegedAction;30import java.security.PrivilegedExceptionAction;31import java.security.PrivilegedActionException;32import sun.util.logging.PlatformLogger;3334/**35* Preferences implementation for Unix. Preferences are stored in the file36* system, with one directory per preferences node. All of the preferences37* at each node are stored in a single file. Atomic file system operations38* (e.g. File.renameTo) are used to ensure integrity. An in-memory cache of39* the "explored" portion of the tree is maintained for performance, and40* written back to the disk periodically. File-locking is used to ensure41* reasonable behavior when multiple VMs are running at the same time.42* (The file lock is obtained only for sync(), flush() and removeNode().)43*44* @author Josh Bloch45* @see Preferences46* @since 1.447*/48@SuppressWarnings("removal")49class FileSystemPreferences extends AbstractPreferences {5051static {52PrivilegedAction<Void> load = () -> {53System.loadLibrary("prefs");54return null;55};56AccessController.doPrivileged(load);57}5859/**60* Sync interval in seconds.61*/62private static final int SYNC_INTERVAL = Math.max(1,63AccessController.doPrivileged((PrivilegedAction<Integer>) () ->64Integer.getInteger("java.util.prefs.syncInterval", 30)));6566/**67* Returns logger for error messages. Backing store exceptions are logged at68* WARNING level.69*/70private static PlatformLogger getLogger() {71return PlatformLogger.getLogger("java.util.prefs");72}7374/**75* Directory for system preferences.76*/77private static File systemRootDir;7879/*80* Flag, indicating whether systemRoot directory is writable81*/82private static boolean isSystemRootWritable;8384/**85* Directory for user preferences.86*/87private static File userRootDir;8889/*90* Flag, indicating whether userRoot directory is writable91*/92private static boolean isUserRootWritable;9394/**95* The user root.96*/97private static volatile Preferences userRoot;9899static Preferences getUserRoot() {100Preferences root = userRoot;101if (root == null) {102synchronized (FileSystemPreferences.class) {103root = userRoot;104if (root == null) {105setupUserRoot();106userRoot = root = new FileSystemPreferences(true);107}108}109}110return root;111}112113private static void setupUserRoot() {114AccessController.doPrivileged(new PrivilegedAction<Void>() {115public Void run() {116userRootDir =117new File(System.getProperty("java.util.prefs.userRoot",118System.getProperty("user.home")), ".java/.userPrefs");119// Attempt to create root dir if it does not yet exist.120if (!userRootDir.exists()) {121if (userRootDir.mkdirs()) {122try {123chmod(userRootDir.getCanonicalPath(), USER_RWX);124} catch (IOException e) {125getLogger().warning("Could not change permissions" +126" on userRoot directory. ");127}128getLogger().info("Created user preferences directory.");129}130else131getLogger().warning("Couldn't create user preferences" +132" directory. User preferences are unusable.");133}134isUserRootWritable = userRootDir.canWrite();135String USER_NAME = System.getProperty("user.name");136userLockFile = new File (userRootDir,".user.lock." + USER_NAME);137userRootModFile = new File (userRootDir,138".userRootModFile." + USER_NAME);139if (!userRootModFile.exists())140try {141// create if does not exist.142userRootModFile.createNewFile();143// Only user can read/write userRootModFile.144int result = chmod(userRootModFile.getCanonicalPath(),145USER_READ_WRITE);146if (result !=0)147getLogger().warning("Problem creating userRoot " +148"mod file. Chmod failed on " +149userRootModFile.getCanonicalPath() +150" Unix error code " + result);151} catch (IOException e) {152getLogger().warning(e.toString());153}154userRootModTime = userRootModFile.lastModified();155return null;156}157});158}159160161/**162* The system root.163*/164private static volatile Preferences systemRoot;165166static Preferences getSystemRoot() {167Preferences root = systemRoot;168if (root == null) {169synchronized (FileSystemPreferences.class) {170root = systemRoot;171if (root == null) {172setupSystemRoot();173systemRoot = root = new FileSystemPreferences(false);174}175}176}177return root;178}179180private static void setupSystemRoot() {181AccessController.doPrivileged(new PrivilegedAction<Void>() {182public Void run() {183String systemPrefsDirName =184System.getProperty("java.util.prefs.systemRoot","/etc/.java");185systemRootDir =186new File(systemPrefsDirName, ".systemPrefs");187// Attempt to create root dir if it does not yet exist.188if (!systemRootDir.exists()) {189// system root does not exist in /etc/.java190// Switching to java.home191systemRootDir =192new File(System.getProperty("java.home"),193".systemPrefs");194if (!systemRootDir.exists()) {195if (systemRootDir.mkdirs()) {196getLogger().info(197"Created system preferences directory "198+ "in java.home.");199try {200chmod(systemRootDir.getCanonicalPath(),201USER_RWX_ALL_RX);202} catch (IOException e) {203}204} else {205getLogger().warning("Could not create "206+ "system preferences directory. System "207+ "preferences are unusable.");208}209}210}211isSystemRootWritable = systemRootDir.canWrite();212systemLockFile = new File(systemRootDir, ".system.lock");213systemRootModFile =214new File (systemRootDir,".systemRootModFile");215if (!systemRootModFile.exists() && isSystemRootWritable)216try {217// create if does not exist.218systemRootModFile.createNewFile();219int result = chmod(systemRootModFile.getCanonicalPath(),220USER_RW_ALL_READ);221if (result !=0)222getLogger().warning("Chmod failed on " +223systemRootModFile.getCanonicalPath() +224" Unix error code " + result);225} catch (IOException e) { getLogger().warning(e.toString());226}227systemRootModTime = systemRootModFile.lastModified();228return null;229}230});231}232233234/**235* Unix user write/read permission236*/237private static final int USER_READ_WRITE = 0600;238239private static final int USER_RW_ALL_READ = 0644;240241242private static final int USER_RWX_ALL_RX = 0755;243244private static final int USER_RWX = 0700;245246/**247* The lock file for the user tree.248*/249static File userLockFile;250251252253/**254* The lock file for the system tree.255*/256static File systemLockFile;257258/**259* Unix lock handle for userRoot.260* Zero, if unlocked.261*/262263private static int userRootLockHandle = 0;264265/**266* Unix lock handle for systemRoot.267* Zero, if unlocked.268*/269270private static int systemRootLockHandle = 0;271272/**273* The directory representing this preference node. There is no guarantee274* that this directory exits, as another VM can delete it at any time275* that it (the other VM) holds the file-lock. While the root node cannot276* be deleted, it may not yet have been created, or the underlying277* directory could have been deleted accidentally.278*/279private final File dir;280281/**282* The file representing this preference node's preferences.283* The file format is undocumented, and subject to change284* from release to release, but I'm sure that you can figure285* it out if you try real hard.286*/287private final File prefsFile;288289/**290* A temporary file used for saving changes to preferences. As part of291* the sync operation, changes are first saved into this file, and then292* atomically renamed to prefsFile. This results in an atomic state293* change from one valid set of preferences to another. The294* the file-lock is held for the duration of this transformation.295*/296private final File tmpFile;297298/**299* File, which keeps track of global modifications of userRoot.300*/301private static File userRootModFile;302303/**304* Flag, which indicated whether userRoot was modified by another VM305*/306private static boolean isUserRootModified = false;307308/**309* Keeps track of userRoot modification time. This time is reset to310* zero after UNIX reboot, and is increased by 1 second each time311* userRoot is modified.312*/313private static long userRootModTime;314315316/*317* File, which keeps track of global modifications of systemRoot318*/319private static File systemRootModFile;320/*321* Flag, which indicates whether systemRoot was modified by another VM322*/323private static boolean isSystemRootModified = false;324325/**326* Keeps track of systemRoot modification time. This time is reset to327* zero after system reboot, and is increased by 1 second each time328* systemRoot is modified.329*/330private static long systemRootModTime;331332/**333* Locally cached preferences for this node (includes uncommitted334* changes). This map is initialized with from disk when the first get or335* put operation occurs on this node. It is synchronized with the336* corresponding disk file (prefsFile) by the sync operation. The initial337* value is read *without* acquiring the file-lock.338*/339private Map<String, String> prefsCache = null;340341/**342* The last modification time of the file backing this node at the time343* that prefCache was last synchronized (or initially read). This344* value is set *before* reading the file, so it's conservative; the345* actual timestamp could be (slightly) higher. A value of zero indicates346* that we were unable to initialize prefsCache from the disk, or347* have not yet attempted to do so. (If prefsCache is non-null, it348* indicates the former; if it's null, the latter.)349*/350private long lastSyncTime = 0;351352/**353* Unix error code for locked file.354*/355private static final int EAGAIN = 11;356357/**358* Unix error code for denied access.359*/360private static final int EACCES = 13;361362/* Used to interpret results of native functions */363private static final int LOCK_HANDLE = 0;364private static final int ERROR_CODE = 1;365366/**367* A list of all uncommitted preference changes. The elements in this368* list are of type PrefChange. If this node is concurrently modified on369* disk by another VM, the two sets of changes are merged when this node370* is sync'ed by overwriting our prefsCache with the preference map last371* written out to disk (by the other VM), and then replaying this change372* log against that map. The resulting map is then written back373* to the disk.374*/375final List<Change> changeLog = new ArrayList<>();376377/**378* Represents a change to a preference.379*/380private abstract class Change {381/**382* Reapplies the change to prefsCache.383*/384abstract void replay();385};386387/**388* Represents a preference put.389*/390private class Put extends Change {391String key, value;392393Put(String key, String value) {394this.key = key;395this.value = value;396}397398void replay() {399prefsCache.put(key, value);400}401}402403/**404* Represents a preference remove.405*/406private class Remove extends Change {407String key;408409Remove(String key) {410this.key = key;411}412413void replay() {414prefsCache.remove(key);415}416}417418/**419* Represents the creation of this node.420*/421private class NodeCreate extends Change {422/**423* Performs no action, but the presence of this object in changeLog424* will force the node and its ancestors to be made permanent at the425* next sync.426*/427void replay() {428}429}430431/**432* NodeCreate object for this node.433*/434NodeCreate nodeCreate = null;435436/**437* Replay changeLog against prefsCache.438*/439private void replayChanges() {440for (int i = 0, n = changeLog.size(); i<n; i++)441changeLog.get(i).replay();442}443444private static Timer syncTimer = new Timer(true); // Daemon Thread445446static {447// Add periodic timer task to periodically sync cached prefs448syncTimer.schedule(new TimerTask() {449public void run() {450syncWorld();451}452}, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000);453454// Add shutdown hook to flush cached prefs on normal termination455AccessController.doPrivileged(new PrivilegedAction<Void>() {456public Void run() {457Runtime.getRuntime().addShutdownHook(458new Thread(null, null, "Sync Timer Thread", 0, false) {459public void run() {460syncTimer.cancel();461syncWorld();462}463});464return null;465}466});467}468469private static void syncWorld() {470/*471* Synchronization necessary because userRoot and systemRoot are472* lazily initialized.473*/474Preferences userRt;475Preferences systemRt;476synchronized(FileSystemPreferences.class) {477userRt = userRoot;478systemRt = systemRoot;479}480481try {482if (userRt != null)483userRt.flush();484} catch(BackingStoreException e) {485getLogger().warning("Couldn't flush user prefs: " + e);486}487488try {489if (systemRt != null)490systemRt.flush();491} catch(BackingStoreException e) {492getLogger().warning("Couldn't flush system prefs: " + e);493}494}495496private final boolean isUserNode;497498/**499* Special constructor for roots (both user and system). This constructor500* will only be called twice, by the static initializer.501*/502private FileSystemPreferences(boolean user) {503super(null, "");504isUserNode = user;505dir = (user ? userRootDir: systemRootDir);506prefsFile = new File(dir, "prefs.xml");507tmpFile = new File(dir, "prefs.tmp");508}509510/**511* Construct a new FileSystemPreferences instance with the specified512* parent node and name. This constructor, called from childSpi,513* is used to make every node except for the two //roots.514*/515private FileSystemPreferences(FileSystemPreferences parent, String name) {516super(parent, name);517isUserNode = parent.isUserNode;518dir = new File(parent.dir, dirName(name));519prefsFile = new File(dir, "prefs.xml");520tmpFile = new File(dir, "prefs.tmp");521AccessController.doPrivileged(new PrivilegedAction<Void>() {522public Void run() {523newNode = !dir.exists();524return null;525}526});527if (newNode) {528// These 2 things guarantee node will get wrtten at next flush/sync529prefsCache = new TreeMap<>();530nodeCreate = new NodeCreate();531changeLog.add(nodeCreate);532}533}534535public boolean isUserNode() {536return isUserNode;537}538539protected void putSpi(String key, String value) {540initCacheIfNecessary();541changeLog.add(new Put(key, value));542prefsCache.put(key, value);543}544545protected String getSpi(String key) {546initCacheIfNecessary();547return prefsCache.get(key);548}549550protected void removeSpi(String key) {551initCacheIfNecessary();552changeLog.add(new Remove(key));553prefsCache.remove(key);554}555556/**557* Initialize prefsCache if it has yet to be initialized. When this method558* returns, prefsCache will be non-null. If the data was successfully559* read from the file, lastSyncTime will be updated. If prefsCache was560* null, but it was impossible to read the file (because it didn't561* exist or for any other reason) prefsCache will be initialized to an562* empty, modifiable Map, and lastSyncTime remain zero.563*/564private void initCacheIfNecessary() {565if (prefsCache != null)566return;567568try {569loadCache();570} catch(Exception e) {571// assert lastSyncTime == 0;572prefsCache = new TreeMap<>();573}574}575576/**577* Attempt to load prefsCache from the backing store. If the attempt578* succeeds, lastSyncTime will be updated (the new value will typically579* correspond to the data loaded into the map, but it may be less,580* if another VM is updating this node concurrently). If the attempt581* fails, a BackingStoreException is thrown and both prefsCache and582* lastSyncTime are unaffected by the call.583*/584private void loadCache() throws BackingStoreException {585try {586AccessController.doPrivileged(587new PrivilegedExceptionAction<Void>() {588public Void run() throws BackingStoreException {589Map<String, String> m = new TreeMap<>();590long newLastSyncTime = 0;591try {592newLastSyncTime = prefsFile.lastModified();593try (FileInputStream fis = new FileInputStream(prefsFile)) {594XmlSupport.importMap(fis, m);595}596} catch(Exception e) {597if (e instanceof InvalidPreferencesFormatException) {598getLogger().warning("Invalid preferences format in "599+ prefsFile.getPath());600prefsFile.renameTo( new File(601prefsFile.getParentFile(),602"IncorrectFormatPrefs.xml"));603m = new TreeMap<>();604} else if (e instanceof FileNotFoundException) {605getLogger().warning("Prefs file removed in background "606+ prefsFile.getPath());607} else {608throw new BackingStoreException(e);609}610}611// Attempt succeeded; update state612prefsCache = m;613lastSyncTime = newLastSyncTime;614return null;615}616});617} catch (PrivilegedActionException e) {618throw (BackingStoreException) e.getException();619}620}621622/**623* Attempt to write back prefsCache to the backing store. If the attempt624* succeeds, lastSyncTime will be updated (the new value will correspond625* exactly to the data thust written back, as we hold the file lock, which626* prevents a concurrent write. If the attempt fails, a627* BackingStoreException is thrown and both the backing store (prefsFile)628* and lastSyncTime will be unaffected by this call. This call will629* NEVER leave prefsFile in a corrupt state.630*/631private void writeBackCache() throws BackingStoreException {632try {633AccessController.doPrivileged(634new PrivilegedExceptionAction<Void>() {635public Void run() throws BackingStoreException {636try {637if (!dir.exists() && !dir.mkdirs())638throw new BackingStoreException(dir +639" create failed.");640try (FileOutputStream fos = new FileOutputStream(tmpFile)) {641XmlSupport.exportMap(fos, prefsCache);642}643if (!tmpFile.renameTo(prefsFile))644throw new BackingStoreException("Can't rename " +645tmpFile + " to " + prefsFile);646} catch(Exception e) {647if (e instanceof BackingStoreException)648throw (BackingStoreException)e;649throw new BackingStoreException(e);650}651return null;652}653});654} catch (PrivilegedActionException e) {655throw (BackingStoreException) e.getException();656}657}658659protected String[] keysSpi() {660initCacheIfNecessary();661return prefsCache.keySet().toArray(new String[prefsCache.size()]);662}663664protected String[] childrenNamesSpi() {665return AccessController.doPrivileged(666new PrivilegedAction<String[]>() {667public String[] run() {668List<String> result = new ArrayList<>();669File[] dirContents = dir.listFiles();670if (dirContents != null) {671for (int i = 0; i < dirContents.length; i++)672if (dirContents[i].isDirectory())673result.add(nodeName(dirContents[i].getName()));674}675return result.toArray(EMPTY_STRING_ARRAY);676}677});678}679680private static final String[] EMPTY_STRING_ARRAY = new String[0];681682protected AbstractPreferences childSpi(String name) {683return new FileSystemPreferences(this, name);684}685686public void removeNode() throws BackingStoreException {687synchronized (isUserNode()? userLockFile: systemLockFile) {688// to remove a node we need an exclusive lock689if (!lockFile(false))690throw(new BackingStoreException("Couldn't get file lock."));691try {692super.removeNode();693} finally {694unlockFile();695}696}697}698699/**700* Called with file lock held (in addition to node locks).701*/702protected void removeNodeSpi() throws BackingStoreException {703try {704AccessController.doPrivileged(705new PrivilegedExceptionAction<Void>() {706public Void run() throws BackingStoreException {707if (changeLog.contains(nodeCreate)) {708changeLog.remove(nodeCreate);709nodeCreate = null;710return null;711}712if (!dir.exists())713return null;714prefsFile.delete();715tmpFile.delete();716// dir should be empty now. If it's not, empty it717File[] junk = dir.listFiles();718if (junk.length != 0) {719getLogger().warning(720"Found extraneous files when removing node: "721+ Arrays.asList(junk));722for (int i=0; i<junk.length; i++)723junk[i].delete();724}725if (!dir.delete())726throw new BackingStoreException("Couldn't delete dir: "727+ dir);728return null;729}730});731} catch (PrivilegedActionException e) {732throw (BackingStoreException) e.getException();733}734}735736public synchronized void sync() throws BackingStoreException {737boolean userNode = isUserNode();738boolean shared;739740if (userNode) {741shared = false; /* use exclusive lock for user prefs */742} else {743/* if can write to system root, use exclusive lock.744otherwise use shared lock. */745shared = !isSystemRootWritable;746}747synchronized (isUserNode()? userLockFile:systemLockFile) {748if (!lockFile(shared))749throw(new BackingStoreException("Couldn't get file lock."));750final Long newModTime =751AccessController.doPrivileged(752new PrivilegedAction<Long>() {753public Long run() {754long nmt;755if (isUserNode()) {756nmt = userRootModFile.lastModified();757isUserRootModified = userRootModTime == nmt;758} else {759nmt = systemRootModFile.lastModified();760isSystemRootModified = systemRootModTime == nmt;761}762return nmt;763}764});765try {766super.sync();767AccessController.doPrivileged(new PrivilegedAction<Void>() {768public Void run() {769if (isUserNode()) {770userRootModTime = newModTime.longValue() + 1000;771userRootModFile.setLastModified(userRootModTime);772} else {773systemRootModTime = newModTime.longValue() + 1000;774systemRootModFile.setLastModified(systemRootModTime);775}776return null;777}778});779} finally {780unlockFile();781}782}783}784785protected void syncSpi() throws BackingStoreException {786try {787AccessController.doPrivileged(788new PrivilegedExceptionAction<Void>() {789public Void run() throws BackingStoreException {790syncSpiPrivileged();791return null;792}793});794} catch (PrivilegedActionException e) {795throw (BackingStoreException) e.getException();796}797}798private void syncSpiPrivileged() throws BackingStoreException {799if (isRemoved())800throw new IllegalStateException("Node has been removed");801if (prefsCache == null)802return; // We've never been used, don't bother syncing803long lastModifiedTime;804if ((isUserNode() ? isUserRootModified : isSystemRootModified)) {805lastModifiedTime = prefsFile.lastModified();806if (lastModifiedTime != lastSyncTime) {807// Prefs at this node were externally modified; read in node and808// playback any local mods since last sync809loadCache();810replayChanges();811lastSyncTime = lastModifiedTime;812}813} else if (lastSyncTime != 0 && !dir.exists()) {814// This node was removed in the background. Playback any changes815// against a virgin (empty) Map.816prefsCache = new TreeMap<>();817replayChanges();818}819if (!changeLog.isEmpty()) {820writeBackCache(); // Creates directory & file if necessary821/*822* Attempt succeeded; it's barely possible that the call to823* lastModified might fail (i.e., return 0), but this would not824* be a disaster, as lastSyncTime is allowed to lag.825*/826lastModifiedTime = prefsFile.lastModified();827/* If lastSyncTime did not change, or went back828* increment by 1 second. Since we hold the lock829* lastSyncTime always monotonically encreases in the830* atomic sense.831*/832if (lastSyncTime <= lastModifiedTime) {833lastSyncTime = lastModifiedTime + 1000;834prefsFile.setLastModified(lastSyncTime);835}836changeLog.clear();837}838}839840public void flush() throws BackingStoreException {841if (isRemoved())842return;843sync();844}845846protected void flushSpi() throws BackingStoreException {847// assert false;848}849850/**851* Returns true if the specified character is appropriate for use in852* Unix directory names. A character is appropriate if it's a printable853* ASCII character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f),854* dot ('.', 0x2e), or underscore ('_', 0x5f).855*/856private static boolean isDirChar(char ch) {857return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_';858}859860/**861* Returns the directory name corresponding to the specified node name.862* Generally, this is just the node name. If the node name includes863* inappropriate characters (as per isDirChar) it is translated to Base64.864* with the underscore character ('_', 0x5f) prepended.865*/866private static String dirName(String nodeName) {867for (int i=0, n=nodeName.length(); i < n; i++)868if (!isDirChar(nodeName.charAt(i)))869return "_" + Base64.byteArrayToAltBase64(byteArray(nodeName));870return nodeName;871}872873/**874* Translate a string into a byte array by translating each character875* into two bytes, high-byte first ("big-endian").876*/877private static byte[] byteArray(String s) {878int len = s.length();879byte[] result = new byte[2*len];880for (int i=0, j=0; i<len; i++) {881char c = s.charAt(i);882result[j++] = (byte) (c>>8);883result[j++] = (byte) c;884}885return result;886}887888/**889* Returns the node name corresponding to the specified directory name.890* (Inverts the transformation of dirName(String).891*/892private static String nodeName(String dirName) {893if (dirName.charAt(0) != '_')894return dirName;895byte a[] = Base64.altBase64ToByteArray(dirName.substring(1));896StringBuffer result = new StringBuffer(a.length/2);897for (int i = 0; i < a.length; ) {898int highByte = a[i++] & 0xff;899int lowByte = a[i++] & 0xff;900result.append((char) ((highByte << 8) | lowByte));901}902return result.toString();903}904905/**906* Try to acquire the appropriate file lock (user or system). If907* the initial attempt fails, several more attempts are made using908* an exponential backoff strategy. If all attempts fail, this method909* returns false.910* @throws SecurityException if file access denied.911*/912private boolean lockFile(boolean shared) throws SecurityException{913boolean usernode = isUserNode();914int[] result;915int errorCode = 0;916File lockFile = (usernode ? userLockFile : systemLockFile);917long sleepTime = INIT_SLEEP_TIME;918for (int i = 0; i < MAX_ATTEMPTS; i++) {919try {920int perm = (usernode? USER_READ_WRITE: USER_RW_ALL_READ);921result = lockFile0(lockFile.getCanonicalPath(), perm, shared);922923errorCode = result[ERROR_CODE];924if (result[LOCK_HANDLE] != 0) {925if (usernode) {926userRootLockHandle = result[LOCK_HANDLE];927} else {928systemRootLockHandle = result[LOCK_HANDLE];929}930return true;931}932} catch(IOException e) {933// // If at first, you don't succeed...934}935936try {937Thread.sleep(sleepTime);938} catch(InterruptedException e) {939checkLockFile0ErrorCode(errorCode);940return false;941}942sleepTime *= 2;943}944checkLockFile0ErrorCode(errorCode);945return false;946}947948/**949* Checks if unlockFile0() returned an error. Throws a SecurityException,950* if access denied. Logs a warning otherwise.951*/952private void checkLockFile0ErrorCode (int errorCode)953throws SecurityException {954if (errorCode == EACCES)955throw new SecurityException("Could not lock " +956(isUserNode()? "User prefs." : "System prefs.") +957" Lock file access denied.");958if (errorCode != EAGAIN)959getLogger().warning("Could not lock " +960(isUserNode()? "User prefs. " : "System prefs.") +961" Unix error code " + errorCode + ".");962}963964/**965* Locks file using UNIX file locking.966* @param fileName Absolute file name of the lock file.967* @return Returns a lock handle, used to unlock the file.968*/969private static native int[]970lockFile0(String fileName, int permission, boolean shared);971972/**973* Unlocks file previously locked by lockFile0().974* @param lockHandle Handle to the file lock.975* @return Returns zero if OK, UNIX error code if failure.976*/977private static native int unlockFile0(int lockHandle);978979/**980* Changes UNIX file permissions.981*/982private static native int chmod(String fileName, int permission);983984/**985* Initial time between lock attempts, in ms. The time is doubled986* after each failing attempt (except the first).987*/988private static int INIT_SLEEP_TIME = 50;989990/**991* Maximum number of lock attempts.992*/993private static int MAX_ATTEMPTS = 5;994995/**996* Release the appropriate file lock (user or system).997* @throws SecurityException if file access denied.998*/999private void unlockFile() {1000int result;1001boolean usernode = isUserNode();1002File lockFile = (usernode ? userLockFile : systemLockFile);1003int lockHandle = ( usernode ? userRootLockHandle:systemRootLockHandle);1004if (lockHandle == 0) {1005getLogger().warning("Unlock: zero lockHandle for " +1006(usernode ? "user":"system") + " preferences.)");1007return;1008}1009result = unlockFile0(lockHandle);1010if (result != 0) {1011getLogger().warning("Could not drop file-lock on " +1012(isUserNode() ? "user" : "system") + " preferences." +1013" Unix error code " + result + ".");1014if (result == EACCES)1015throw new SecurityException("Could not unlock" +1016(isUserNode()? "User prefs." : "System prefs.") +1017" Lock file access denied.");1018}1019if (isUserNode()) {1020userRootLockHandle = 0;1021} else {1022systemRootLockHandle = 0;1023}1024}1025}102610271028