Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/solaris/classes/java/util/prefs/FileSystemPreferences.java
32288 views
/*1* Copyright (c) 2000, 2018, 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;3233import sun.util.logging.PlatformLogger;3435/**36* Preferences implementation for Unix. Preferences are stored in the file37* system, with one directory per preferences node. All of the preferences38* at each node are stored in a single file. Atomic file system operations39* (e.g. File.renameTo) are used to ensure integrity. An in-memory cache of40* the "explored" portion of the tree is maintained for performance, and41* written back to the disk periodically. File-locking is used to ensure42* 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 Bloch46* @see Preferences47* @since 1.448*/49class FileSystemPreferences extends AbstractPreferences {50/**51* Sync interval in seconds.52*/53private static final int SYNC_INTERVAL = Math.max(1,54Integer.parseInt(55AccessController.doPrivileged(56new sun.security.action.GetPropertyAction(57"java.util.prefs.syncInterval", "30"))));5859/**60* Returns logger for error messages. Backing store exceptions are logged at61* WARNING level.62*/63private static PlatformLogger getLogger() {64return PlatformLogger.getLogger("java.util.prefs");65}6667/**68* Directory for system preferences.69*/70private static File systemRootDir;7172/*73* Flag, indicating whether systemRoot directory is writable74*/75private static boolean isSystemRootWritable;7677/**78* Directory for user preferences.79*/80private static File userRootDir;8182/*83* Flag, indicating whether userRoot directory is writable84*/85private static boolean isUserRootWritable;8687/**88* The user root.89*/90private static volatile Preferences userRoot;9192static Preferences getUserRoot() {93Preferences root = userRoot;94if (root == null) {95synchronized (FileSystemPreferences.class) {96root = userRoot;97if (root == null) {98setupUserRoot();99userRoot = root = new FileSystemPreferences(true);100}101}102}103return root;104}105106private static void setupUserRoot() {107AccessController.doPrivileged(new PrivilegedAction<Void>() {108public Void run() {109userRootDir =110new File(System.getProperty("java.util.prefs.userRoot",111System.getProperty("user.home")), ".java/.userPrefs");112// Attempt to create root dir if it does not yet exist.113if (!userRootDir.exists()) {114if (userRootDir.mkdirs()) {115try {116chmod(userRootDir.getCanonicalPath(), USER_RWX);117} catch (IOException e) {118getLogger().warning("Could not change permissions" +119" on userRoot directory. ");120}121getLogger().info("Created user preferences directory.");122}123else124getLogger().warning("Couldn't create user preferences" +125" directory. User preferences are unusable.");126}127isUserRootWritable = userRootDir.canWrite();128String USER_NAME = System.getProperty("user.name");129userLockFile = new File (userRootDir,".user.lock." + USER_NAME);130userRootModFile = new File (userRootDir,131".userRootModFile." + USER_NAME);132if (!userRootModFile.exists())133try {134// create if does not exist.135userRootModFile.createNewFile();136// Only user can read/write userRootModFile.137int result = chmod(userRootModFile.getCanonicalPath(),138USER_READ_WRITE);139if (result !=0)140getLogger().warning("Problem creating userRoot " +141"mod file. Chmod failed on " +142userRootModFile.getCanonicalPath() +143" Unix error code " + result);144} catch (IOException e) {145getLogger().warning(e.toString());146}147userRootModTime = userRootModFile.lastModified();148return null;149}150});151}152153154/**155* The system root.156*/157private static volatile Preferences systemRoot;158159static Preferences getSystemRoot() {160Preferences root = systemRoot;161if (root == null) {162synchronized (FileSystemPreferences.class) {163root = systemRoot;164if (root == null) {165setupSystemRoot();166systemRoot = root = new FileSystemPreferences(false);167}168}169}170return root;171}172173private static void setupSystemRoot() {174AccessController.doPrivileged(new PrivilegedAction<Void>() {175public Void run() {176String systemPrefsDirName =177System.getProperty("java.util.prefs.systemRoot","/etc/.java");178systemRootDir =179new File(systemPrefsDirName, ".systemPrefs");180// Attempt to create root dir if it does not yet exist.181if (!systemRootDir.exists()) {182// system root does not exist in /etc/.java183// Switching to java.home184systemRootDir =185new File(System.getProperty("java.home"),186".systemPrefs");187if (!systemRootDir.exists()) {188if (systemRootDir.mkdirs()) {189getLogger().info(190"Created system preferences directory "191+ "in java.home.");192try {193chmod(systemRootDir.getCanonicalPath(),194USER_RWX_ALL_RX);195} catch (IOException e) {196}197} else {198getLogger().warning("Could not create "199+ "system preferences directory. System "200+ "preferences are unusable.");201}202}203}204isSystemRootWritable = systemRootDir.canWrite();205systemLockFile = new File(systemRootDir, ".system.lock");206systemRootModFile =207new File (systemRootDir,".systemRootModFile");208if (!systemRootModFile.exists() && isSystemRootWritable)209try {210// create if does not exist.211systemRootModFile.createNewFile();212int result = chmod(systemRootModFile.getCanonicalPath(),213USER_RW_ALL_READ);214if (result !=0)215getLogger().warning("Chmod failed on " +216systemRootModFile.getCanonicalPath() +217" Unix error code " + result);218} catch (IOException e) { getLogger().warning(e.toString());219}220systemRootModTime = systemRootModFile.lastModified();221return null;222}223});224}225226227/**228* Unix user write/read permission229*/230private static final int USER_READ_WRITE = 0600;231232private static final int USER_RW_ALL_READ = 0644;233234235private static final int USER_RWX_ALL_RX = 0755;236237private static final int USER_RWX = 0700;238239/**240* The lock file for the user tree.241*/242static File userLockFile;243244245246/**247* The lock file for the system tree.248*/249static File systemLockFile;250251/**252* Unix lock handle for userRoot.253* Zero, if unlocked.254*/255256private static int userRootLockHandle = 0;257258/**259* Unix lock handle for systemRoot.260* Zero, if unlocked.261*/262263private static int systemRootLockHandle = 0;264265/**266* The directory representing this preference node. There is no guarantee267* that this directory exits, as another VM can delete it at any time268* that it (the other VM) holds the file-lock. While the root node cannot269* be deleted, it may not yet have been created, or the underlying270* directory could have been deleted accidentally.271*/272private final File dir;273274/**275* The file representing this preference node's preferences.276* The file format is undocumented, and subject to change277* from release to release, but I'm sure that you can figure278* it out if you try real hard.279*/280private final File prefsFile;281282/**283* A temporary file used for saving changes to preferences. As part of284* the sync operation, changes are first saved into this file, and then285* atomically renamed to prefsFile. This results in an atomic state286* change from one valid set of preferences to another. The287* the file-lock is held for the duration of this transformation.288*/289private final File tmpFile;290291/**292* File, which keeps track of global modifications of userRoot.293*/294private static File userRootModFile;295296/**297* Flag, which indicated whether userRoot was modified by another VM298*/299private static boolean isUserRootModified = false;300301/**302* Keeps track of userRoot modification time. This time is reset to303* zero after UNIX reboot, and is increased by 1 second each time304* userRoot is modified.305*/306private static long userRootModTime;307308309/*310* File, which keeps track of global modifications of systemRoot311*/312private static File systemRootModFile;313/*314* Flag, which indicates whether systemRoot was modified by another VM315*/316private static boolean isSystemRootModified = false;317318/**319* Keeps track of systemRoot modification time. This time is reset to320* zero after system reboot, and is increased by 1 second each time321* systemRoot is modified.322*/323private static long systemRootModTime;324325/**326* Locally cached preferences for this node (includes uncommitted327* changes). This map is initialized with from disk when the first get or328* put operation occurs on this node. It is synchronized with the329* corresponding disk file (prefsFile) by the sync operation. The initial330* value is read *without* acquiring the file-lock.331*/332private Map<String, String> prefsCache = null;333334/**335* The last modification time of the file backing this node at the time336* that prefCache was last synchronized (or initially read). This337* value is set *before* reading the file, so it's conservative; the338* actual timestamp could be (slightly) higher. A value of zero indicates339* that we were unable to initialize prefsCache from the disk, or340* have not yet attempted to do so. (If prefsCache is non-null, it341* indicates the former; if it's null, the latter.)342*/343private long lastSyncTime = 0;344345/**346* Unix error code for locked file.347*/348private static final int EAGAIN = 11;349350/**351* Unix error code for denied access.352*/353private static final int EACCES = 13;354355/* Used to interpret results of native functions */356private static final int LOCK_HANDLE = 0;357private static final int ERROR_CODE = 1;358359/**360* A list of all uncommitted preference changes. The elements in this361* list are of type PrefChange. If this node is concurrently modified on362* disk by another VM, the two sets of changes are merged when this node363* is sync'ed by overwriting our prefsCache with the preference map last364* written out to disk (by the other VM), and then replaying this change365* log against that map. The resulting map is then written back366* to the disk.367*/368final List<Change> changeLog = new ArrayList<>();369370/**371* Represents a change to a preference.372*/373private abstract class Change {374/**375* Reapplies the change to prefsCache.376*/377abstract void replay();378};379380/**381* Represents a preference put.382*/383private class Put extends Change {384String key, value;385386Put(String key, String value) {387this.key = key;388this.value = value;389}390391void replay() {392prefsCache.put(key, value);393}394}395396/**397* Represents a preference remove.398*/399private class Remove extends Change {400String key;401402Remove(String key) {403this.key = key;404}405406void replay() {407prefsCache.remove(key);408}409}410411/**412* Represents the creation of this node.413*/414private class NodeCreate extends Change {415/**416* Performs no action, but the presence of this object in changeLog417* will force the node and its ancestors to be made permanent at the418* next sync.419*/420void replay() {421}422}423424/**425* NodeCreate object for this node.426*/427NodeCreate nodeCreate = null;428429/**430* Replay changeLog against prefsCache.431*/432private void replayChanges() {433for (int i = 0, n = changeLog.size(); i<n; i++)434changeLog.get(i).replay();435}436437private static Timer syncTimer = new Timer(true); // Daemon Thread438439static {440// Add periodic timer task to periodically sync cached prefs441syncTimer.schedule(new TimerTask() {442public void run() {443syncWorld();444}445}, SYNC_INTERVAL*1000, SYNC_INTERVAL*1000);446447// Add shutdown hook to flush cached prefs on normal termination448AccessController.doPrivileged(new PrivilegedAction<Void>() {449public Void run() {450Runtime.getRuntime().addShutdownHook(new Thread() {451public void run() {452syncTimer.cancel();453syncWorld();454}455});456return null;457}458});459}460461private static void syncWorld() {462/*463* Synchronization necessary because userRoot and systemRoot are464* lazily initialized.465*/466Preferences userRt;467Preferences systemRt;468synchronized(FileSystemPreferences.class) {469userRt = userRoot;470systemRt = systemRoot;471}472473try {474if (userRt != null)475userRt.flush();476} catch(BackingStoreException e) {477getLogger().warning("Couldn't flush user prefs: " + e);478}479480try {481if (systemRt != null)482systemRt.flush();483} catch(BackingStoreException e) {484getLogger().warning("Couldn't flush system prefs: " + e);485}486}487488private final boolean isUserNode;489490/**491* Special constructor for roots (both user and system). This constructor492* will only be called twice, by the static initializer.493*/494private FileSystemPreferences(boolean user) {495super(null, "");496isUserNode = user;497dir = (user ? userRootDir: systemRootDir);498prefsFile = new File(dir, "prefs.xml");499tmpFile = new File(dir, "prefs.tmp");500}501502/**503* Construct a new FileSystemPreferences instance with the specified504* parent node and name. This constructor, called from childSpi,505* is used to make every node except for the two //roots.506*/507private FileSystemPreferences(FileSystemPreferences parent, String name) {508super(parent, name);509isUserNode = parent.isUserNode;510dir = new File(parent.dir, dirName(name));511prefsFile = new File(dir, "prefs.xml");512tmpFile = new File(dir, "prefs.tmp");513AccessController.doPrivileged(new PrivilegedAction<Void>() {514public Void run() {515newNode = !dir.exists();516return null;517}518});519if (newNode) {520// These 2 things guarantee node will get wrtten at next flush/sync521prefsCache = new TreeMap<>();522nodeCreate = new NodeCreate();523changeLog.add(nodeCreate);524}525}526527public boolean isUserNode() {528return isUserNode;529}530531protected void putSpi(String key, String value) {532initCacheIfNecessary();533changeLog.add(new Put(key, value));534prefsCache.put(key, value);535}536537protected String getSpi(String key) {538initCacheIfNecessary();539return prefsCache.get(key);540}541542protected void removeSpi(String key) {543initCacheIfNecessary();544changeLog.add(new Remove(key));545prefsCache.remove(key);546}547548/**549* Initialize prefsCache if it has yet to be initialized. When this method550* returns, prefsCache will be non-null. If the data was successfully551* read from the file, lastSyncTime will be updated. If prefsCache was552* null, but it was impossible to read the file (because it didn't553* exist or for any other reason) prefsCache will be initialized to an554* empty, modifiable Map, and lastSyncTime remain zero.555*/556private void initCacheIfNecessary() {557if (prefsCache != null)558return;559560try {561loadCache();562} catch(Exception e) {563// assert lastSyncTime == 0;564prefsCache = new TreeMap<>();565}566}567568/**569* Attempt to load prefsCache from the backing store. If the attempt570* succeeds, lastSyncTime will be updated (the new value will typically571* correspond to the data loaded into the map, but it may be less,572* if another VM is updating this node concurrently). If the attempt573* fails, a BackingStoreException is thrown and both prefsCache and574* lastSyncTime are unaffected by the call.575*/576private void loadCache() throws BackingStoreException {577try {578AccessController.doPrivileged(579new PrivilegedExceptionAction<Void>() {580public Void run() throws BackingStoreException {581Map<String, String> m = new TreeMap<>();582long newLastSyncTime = 0;583try {584newLastSyncTime = prefsFile.lastModified();585try (FileInputStream fis = new FileInputStream(prefsFile)) {586XmlSupport.importMap(fis, m);587}588} catch(Exception e) {589if (e instanceof InvalidPreferencesFormatException) {590getLogger().warning("Invalid preferences format in "591+ prefsFile.getPath());592prefsFile.renameTo( new File(593prefsFile.getParentFile(),594"IncorrectFormatPrefs.xml"));595m = new TreeMap<>();596} else if (e instanceof FileNotFoundException) {597getLogger().warning("Prefs file removed in background "598+ prefsFile.getPath());599} else {600throw new BackingStoreException(e);601}602}603// Attempt succeeded; update state604prefsCache = m;605lastSyncTime = newLastSyncTime;606return null;607}608});609} catch (PrivilegedActionException e) {610throw (BackingStoreException) e.getException();611}612}613614/**615* Attempt to write back prefsCache to the backing store. If the attempt616* succeeds, lastSyncTime will be updated (the new value will correspond617* exactly to the data thust written back, as we hold the file lock, which618* prevents a concurrent write. If the attempt fails, a619* BackingStoreException is thrown and both the backing store (prefsFile)620* and lastSyncTime will be unaffected by this call. This call will621* NEVER leave prefsFile in a corrupt state.622*/623private void writeBackCache() throws BackingStoreException {624try {625AccessController.doPrivileged(626new PrivilegedExceptionAction<Void>() {627public Void run() throws BackingStoreException {628try {629if (!dir.exists() && !dir.mkdirs())630throw new BackingStoreException(dir +631" create failed.");632try (FileOutputStream fos = new FileOutputStream(tmpFile)) {633XmlSupport.exportMap(fos, prefsCache);634}635if (!tmpFile.renameTo(prefsFile))636throw new BackingStoreException("Can't rename " +637tmpFile + " to " + prefsFile);638} catch(Exception e) {639if (e instanceof BackingStoreException)640throw (BackingStoreException)e;641throw new BackingStoreException(e);642}643return null;644}645});646} catch (PrivilegedActionException e) {647throw (BackingStoreException) e.getException();648}649}650651protected String[] keysSpi() {652initCacheIfNecessary();653return prefsCache.keySet().toArray(new String[prefsCache.size()]);654}655656protected String[] childrenNamesSpi() {657return AccessController.doPrivileged(658new PrivilegedAction<String[]>() {659public String[] run() {660List<String> result = new ArrayList<>();661File[] dirContents = dir.listFiles();662if (dirContents != null) {663for (int i = 0; i < dirContents.length; i++)664if (dirContents[i].isDirectory())665result.add(nodeName(dirContents[i].getName()));666}667return result.toArray(EMPTY_STRING_ARRAY);668}669});670}671672private static final String[] EMPTY_STRING_ARRAY = new String[0];673674protected AbstractPreferences childSpi(String name) {675return new FileSystemPreferences(this, name);676}677678public void removeNode() throws BackingStoreException {679synchronized (isUserNode()? userLockFile: systemLockFile) {680// to remove a node we need an exclusive lock681if (!lockFile(false))682throw(new BackingStoreException("Couldn't get file lock."));683try {684super.removeNode();685} finally {686unlockFile();687}688}689}690691/**692* Called with file lock held (in addition to node locks).693*/694protected void removeNodeSpi() throws BackingStoreException {695try {696AccessController.doPrivileged(697new PrivilegedExceptionAction<Void>() {698public Void run() throws BackingStoreException {699if (changeLog.contains(nodeCreate)) {700changeLog.remove(nodeCreate);701nodeCreate = null;702return null;703}704if (!dir.exists())705return null;706prefsFile.delete();707tmpFile.delete();708// dir should be empty now. If it's not, empty it709File[] junk = dir.listFiles();710if (junk.length != 0) {711getLogger().warning(712"Found extraneous files when removing node: "713+ Arrays.asList(junk));714for (int i=0; i<junk.length; i++)715junk[i].delete();716}717if (!dir.delete())718throw new BackingStoreException("Couldn't delete dir: "719+ dir);720return null;721}722});723} catch (PrivilegedActionException e) {724throw (BackingStoreException) e.getException();725}726}727728public synchronized void sync() throws BackingStoreException {729boolean userNode = isUserNode();730boolean shared;731732if (userNode) {733shared = false; /* use exclusive lock for user prefs */734} else {735/* if can write to system root, use exclusive lock.736otherwise use shared lock. */737shared = !isSystemRootWritable;738}739synchronized (isUserNode()? userLockFile:systemLockFile) {740if (!lockFile(shared))741throw(new BackingStoreException("Couldn't get file lock."));742final Long newModTime =743AccessController.doPrivileged(744new PrivilegedAction<Long>() {745public Long run() {746long nmt;747if (isUserNode()) {748nmt = userRootModFile.lastModified();749isUserRootModified = userRootModTime == nmt;750} else {751nmt = systemRootModFile.lastModified();752isSystemRootModified = systemRootModTime == nmt;753}754return new Long(nmt);755}756});757try {758super.sync();759AccessController.doPrivileged(new PrivilegedAction<Void>() {760public Void run() {761if (isUserNode()) {762userRootModTime = newModTime.longValue() + 1000;763userRootModFile.setLastModified(userRootModTime);764} else {765systemRootModTime = newModTime.longValue() + 1000;766systemRootModFile.setLastModified(systemRootModTime);767}768return null;769}770});771} finally {772unlockFile();773}774}775}776777protected void syncSpi() throws BackingStoreException {778try {779AccessController.doPrivileged(780new PrivilegedExceptionAction<Void>() {781public Void run() throws BackingStoreException {782syncSpiPrivileged();783return null;784}785});786} catch (PrivilegedActionException e) {787throw (BackingStoreException) e.getException();788}789}790private void syncSpiPrivileged() throws BackingStoreException {791if (isRemoved())792throw new IllegalStateException("Node has been removed");793if (prefsCache == null)794return; // We've never been used, don't bother syncing795long lastModifiedTime;796if ((isUserNode() ? isUserRootModified : isSystemRootModified)) {797lastModifiedTime = prefsFile.lastModified();798if (lastModifiedTime != lastSyncTime) {799// Prefs at this node were externally modified; read in node and800// playback any local mods since last sync801loadCache();802replayChanges();803lastSyncTime = lastModifiedTime;804}805} else if (lastSyncTime != 0 && !dir.exists()) {806// This node was removed in the background. Playback any changes807// against a virgin (empty) Map.808prefsCache = new TreeMap<>();809replayChanges();810}811if (!changeLog.isEmpty()) {812writeBackCache(); // Creates directory & file if necessary813/*814* Attempt succeeded; it's barely possible that the call to815* lastModified might fail (i.e., return 0), but this would not816* be a disaster, as lastSyncTime is allowed to lag.817*/818lastModifiedTime = prefsFile.lastModified();819/* If lastSyncTime did not change, or went back820* increment by 1 second. Since we hold the lock821* lastSyncTime always monotonically encreases in the822* atomic sense.823*/824if (lastSyncTime <= lastModifiedTime) {825lastSyncTime = lastModifiedTime + 1000;826prefsFile.setLastModified(lastSyncTime);827}828changeLog.clear();829}830}831832public void flush() throws BackingStoreException {833if (isRemoved())834return;835sync();836}837838protected void flushSpi() throws BackingStoreException {839// assert false;840}841842/**843* Returns true if the specified character is appropriate for use in844* Unix directory names. A character is appropriate if it's a printable845* ASCII character (> 0x1f && < 0x7f) and unequal to slash ('/', 0x2f),846* dot ('.', 0x2e), or underscore ('_', 0x5f).847*/848private static boolean isDirChar(char ch) {849return ch > 0x1f && ch < 0x7f && ch != '/' && ch != '.' && ch != '_';850}851852/**853* Returns the directory name corresponding to the specified node name.854* Generally, this is just the node name. If the node name includes855* inappropriate characters (as per isDirChar) it is translated to Base64.856* with the underscore character ('_', 0x5f) prepended.857*/858private static String dirName(String nodeName) {859for (int i=0, n=nodeName.length(); i < n; i++)860if (!isDirChar(nodeName.charAt(i)))861return "_" + Base64.byteArrayToAltBase64(byteArray(nodeName));862return nodeName;863}864865/**866* Translate a string into a byte array by translating each character867* into two bytes, high-byte first ("big-endian").868*/869private static byte[] byteArray(String s) {870int len = s.length();871byte[] result = new byte[2*len];872for (int i=0, j=0; i<len; i++) {873char c = s.charAt(i);874result[j++] = (byte) (c>>8);875result[j++] = (byte) c;876}877return result;878}879880/**881* Returns the node name corresponding to the specified directory name.882* (Inverts the transformation of dirName(String).883*/884private static String nodeName(String dirName) {885if (dirName.charAt(0) != '_')886return dirName;887byte a[] = Base64.altBase64ToByteArray(dirName.substring(1));888StringBuffer result = new StringBuffer(a.length/2);889for (int i = 0; i < a.length; ) {890int highByte = a[i++] & 0xff;891int lowByte = a[i++] & 0xff;892result.append((char) ((highByte << 8) | lowByte));893}894return result.toString();895}896897/**898* Try to acquire the appropriate file lock (user or system). If899* the initial attempt fails, several more attempts are made using900* an exponential backoff strategy. If all attempts fail, this method901* returns false.902* @throws SecurityException if file access denied.903*/904private boolean lockFile(boolean shared) throws SecurityException{905boolean usernode = isUserNode();906int[] result;907int errorCode = 0;908File lockFile = (usernode ? userLockFile : systemLockFile);909long sleepTime = INIT_SLEEP_TIME;910for (int i = 0; i < MAX_ATTEMPTS; i++) {911try {912int perm = (usernode? USER_READ_WRITE: USER_RW_ALL_READ);913result = lockFile0(lockFile.getCanonicalPath(), perm, shared);914915errorCode = result[ERROR_CODE];916if (result[LOCK_HANDLE] != 0) {917if (usernode) {918userRootLockHandle = result[LOCK_HANDLE];919} else {920systemRootLockHandle = result[LOCK_HANDLE];921}922return true;923}924} catch(IOException e) {925// // If at first, you don't succeed...926}927928try {929Thread.sleep(sleepTime);930} catch(InterruptedException e) {931checkLockFile0ErrorCode(errorCode);932return false;933}934sleepTime *= 2;935}936checkLockFile0ErrorCode(errorCode);937return false;938}939940/**941* Checks if unlockFile0() returned an error. Throws a SecurityException,942* if access denied. Logs a warning otherwise.943*/944private void checkLockFile0ErrorCode (int errorCode)945throws SecurityException {946if (errorCode == EACCES)947throw new SecurityException("Could not lock " +948(isUserNode()? "User prefs." : "System prefs.") +949" Lock file access denied.");950if (errorCode != EAGAIN)951getLogger().warning("Could not lock " +952(isUserNode()? "User prefs. " : "System prefs.") +953" Unix error code " + errorCode + ".");954}955956/**957* Locks file using UNIX file locking.958* @param fileName Absolute file name of the lock file.959* @return Returns a lock handle, used to unlock the file.960*/961private static native int[]962lockFile0(String fileName, int permission, boolean shared);963964/**965* Unlocks file previously locked by lockFile0().966* @param lockHandle Handle to the file lock.967* @return Returns zero if OK, UNIX error code if failure.968*/969private static native int unlockFile0(int lockHandle);970971/**972* Changes UNIX file permissions.973*/974private static native int chmod(String fileName, int permission);975976/**977* Initial time between lock attempts, in ms. The time is doubled978* after each failing attempt (except the first).979*/980private static int INIT_SLEEP_TIME = 50;981982/**983* Maximum number of lock attempts.984*/985private static int MAX_ATTEMPTS = 5;986987/**988* Release the the appropriate file lock (user or system).989* @throws SecurityException if file access denied.990*/991private void unlockFile() {992int result;993boolean usernode = isUserNode();994File lockFile = (usernode ? userLockFile : systemLockFile);995int lockHandle = ( usernode ? userRootLockHandle:systemRootLockHandle);996if (lockHandle == 0) {997getLogger().warning("Unlock: zero lockHandle for " +998(usernode ? "user":"system") + " preferences.)");999return;1000}1001result = unlockFile0(lockHandle);1002if (result != 0) {1003getLogger().warning("Could not drop file-lock on " +1004(isUserNode() ? "user" : "system") + " preferences." +1005" Unix error code " + result + ".");1006if (result == EACCES)1007throw new SecurityException("Could not unlock" +1008(isUserNode()? "User prefs." : "System prefs.") +1009" Lock file access denied.");1010}1011if (isUserNode()) {1012userRootLockHandle = 0;1013} else {1014systemRootLockHandle = 0;1015}1016}1017}101810191020