1 /*
   2  * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.sjavac.server;
  27 
  28 import java.io.File;
  29 import java.io.FileNotFoundException;
  30 import java.io.IOException;
  31 import java.io.RandomAccessFile;
  32 import java.nio.channels.ClosedChannelException;
  33 import java.nio.channels.FileChannel;
  34 import java.nio.channels.FileLock;
  35 import java.nio.channels.FileLockInterruptionException;
  36 import java.util.concurrent.Semaphore;
  37 
  38 import com.sun.tools.javac.util.Assert;
  39 import com.sun.tools.sjavac.Log;
  40 import com.sun.tools.sjavac.client.PortFileInaccessibleException;
  41 
  42 /**
  43  * The PortFile class mediates access to a short binary file containing the tcp/ip port (for the localhost)
  44  * and a cookie necessary for the server answering on that port. The file can be locked using file system
  45  * primitives to avoid race conditions when several javac clients are started at the same. Note that file
  46  * system locking is not always supported on a all operating systems and/or file systems.
  47  *
  48  *  <p><b>This is NOT part of any supported API.
  49  *  If you write code that depends on this, you do so at your own risk.
  50  *  This code and its internal interfaces are subject to change or
  51  *  deletion without notice.</b>
  52  */
  53 public class PortFile {
  54 
  55     // Port file format:
  56     // byte ordering: high byte first = big endian
  57     // Magic nr, 4 byte int, first in file.
  58     private final static int magicNr = 0x1174;
  59     // Followed by a 4 byte int, with the port nr.
  60     // Followed by a 8 byte long, with cookie nr.
  61 
  62     private String filename;
  63     private File file;
  64     private File stopFile;
  65     private RandomAccessFile rwfile;
  66     private FileChannel channel;
  67 
  68     // FileLock used to solve inter JVM synchronization, lockSem used to avoid
  69     // JVM internal OverlappingFileLockExceptions.
  70     // Class invariant: lock.isValid() <-> lockSem.availablePermits() == 0
  71     private FileLock lock;
  72     private Semaphore lockSem = new Semaphore(1);
  73 
  74     private boolean containsPortInfo;
  75     private int serverPort;
  76     private long serverCookie;
  77     private int myServerPort;
  78     private long myServerCookie;
  79 
  80     /**
  81      * Create a new portfile.
  82      * @param fn is the path to the file.
  83      */
  84     public PortFile(String fn) {
  85         filename = fn;
  86         file = new File(filename);
  87         stopFile = new File(filename+".stop");
  88         containsPortInfo = false;
  89         lock = null;
  90     }
  91 
  92     private void initializeChannel() throws PortFileInaccessibleException {
  93         try {
  94             rwfile = new RandomAccessFile(file, "rw");
  95         } catch (FileNotFoundException e) {
  96             // Reached if file for instance already exists and is a directory
  97             throw new PortFileInaccessibleException(e);
  98         }
  99         // The rwfile should only be readable by the owner of the process
 100         // and no other! How do we do that on a RandomAccessFile?
 101         channel = rwfile.getChannel();
 102     }
 103 
 104     /**
 105      * Lock the port file.
 106      */
 107     public void lock() throws IOException, InterruptedException {
 108         if (channel == null) {
 109             initializeChannel();
 110         }
 111         lockSem.acquire();
 112         lock = channel.lock();
 113     }
 114 
 115     /**
 116      * Read the values from the port file in the file system.
 117      * Expects the port file to be locked.
 118      */
 119     public void getValues()  {
 120         containsPortInfo = false;
 121         if (lock == null) {
 122             // Not locked, remain ignorant about port file contents.
 123             return;
 124         }
 125         try {
 126             if (rwfile.length()>0) {
 127                 rwfile.seek(0);
 128                 int nr = rwfile.readInt();
 129                 serverPort = rwfile.readInt();
 130                 serverCookie = rwfile.readLong();
 131 
 132                 if (nr == magicNr) {
 133                     containsPortInfo = true;
 134                 } else {
 135                     containsPortInfo = false;
 136                 }
 137             }
 138         } catch (IOException e) {
 139             containsPortInfo = false;
 140         }
 141     }
 142 
 143     /**
 144      * Did the locking and getValues succeed?
 145      */
 146     public boolean containsPortInfo() {
 147         return containsPortInfo;
 148     }
 149 
 150     /**
 151      * If so, then we can acquire the tcp/ip port on localhost.
 152      */
 153     public int getPort() {
 154         Assert.check(containsPortInfo);
 155         return serverPort;
 156     }
 157 
 158     /**
 159      * If so, then we can acquire the server cookie.
 160      */
 161     public long getCookie() {
 162         Assert.check(containsPortInfo);
 163         return serverCookie;
 164     }
 165 
 166     /**
 167      * Store the values into the locked port file.
 168      */
 169     public void setValues(int port, long cookie) throws IOException {
 170         Assert.check(lock != null);
 171         rwfile.seek(0);
 172         // Write the magic nr that identifies a port file.
 173         rwfile.writeInt(magicNr);
 174         rwfile.writeInt(port);
 175         rwfile.writeLong(cookie);
 176         myServerPort = port;
 177         myServerCookie = cookie;
 178     }
 179 
 180     /**
 181      * Delete the port file.
 182      */
 183     public void delete() throws IOException, InterruptedException {
 184         // Access to file must be closed before deleting.
 185         rwfile.close();
 186 
 187         file.delete();
 188 
 189         // Wait until file has been deleted (deletes are asynchronous on Windows!) otherwise we
 190         // might shutdown the server and prevent another one from starting.
 191         for (int i = 0; i < 10 && file.exists(); i++) {
 192             Thread.sleep(1000);
 193         }
 194         if (file.exists()) {
 195             throw new IOException("Failed to delete file.");
 196         }
 197     }
 198 
 199     /**
 200      * Is the port file still there?
 201      */
 202     public boolean exists() throws IOException {
 203         return file.exists();
 204     }
 205 
 206     /**
 207      * Is a stop file there?
 208      */
 209     public boolean markedForStop() throws IOException {
 210         if (stopFile.exists()) {
 211             try {
 212                 stopFile.delete();
 213             } catch (Exception e) {
 214             }
 215             return true;
 216         }
 217         return false;
 218     }
 219 
 220     /**
 221      * Unlock the port file.
 222      */
 223     public void unlock() throws IOException {
 224         if (lock == null) {
 225             return;
 226         }
 227         lock.release();
 228         lock = null;
 229         lockSem.release();
 230     }
 231 
 232     /**
 233      * Wait for the port file to contain values that look valid.
 234      */
 235     public void waitForValidValues() throws IOException, InterruptedException {
 236         final int MS_BETWEEN_ATTEMPTS = 500;
 237         long startTime = System.currentTimeMillis();
 238         long timeout = startTime + getServerStartupTimeoutSeconds() * 1000;
 239         while (true) {
 240             Log.debug("Looking for valid port file values...");
 241             if (exists()) {
 242                 lock();
 243                 getValues();
 244                 unlock();
 245             }
 246             if (containsPortInfo) {
 247                 Log.debug("Valid port file values found after " + (System.currentTimeMillis() - startTime) + " ms");
 248                 return;
 249             }
 250             if (System.currentTimeMillis() > timeout) {
 251                 break;
 252             }
 253             Thread.sleep(MS_BETWEEN_ATTEMPTS);
 254         }
 255         throw new IOException("No port file values materialized. Giving up after " +
 256                                       (System.currentTimeMillis() - startTime) + " ms");
 257     }
 258 
 259     /**
 260      * Check if the portfile still contains my values, assuming that I am the server.
 261      */
 262     public boolean stillMyValues() throws IOException, FileNotFoundException, InterruptedException {
 263         for (;;) {
 264             try {
 265                 lock();
 266                 getValues();
 267                 unlock();
 268                 if (containsPortInfo) {
 269                     if (serverPort == myServerPort &&
 270                         serverCookie == myServerCookie) {
 271                         // Everything is ok.
 272                         return true;
 273                     }
 274                     // Someone has overwritten the port file.
 275                     // Probably another javac server, lets quit.
 276                     return false;
 277                 }
 278                 // Something else is wrong with the portfile. Lets quit.
 279                 return false;
 280             } catch (FileLockInterruptionException e) {
 281                 continue;
 282             }
 283             catch (ClosedChannelException e) {
 284                 // The channel has been closed since sjavac is exiting.
 285                 return false;
 286             }
 287         }
 288     }
 289 
 290     /**
 291      * Return the name of the port file.
 292      */
 293     public String getFilename() {
 294         return filename;
 295     }
 296 
 297     private long getServerStartupTimeoutSeconds() {
 298         String str = System.getProperty("serverStartupTimeout");
 299         if (str != null) {
 300             try {
 301                 return Integer.parseInt(str);
 302             } catch (NumberFormatException e) {
 303             }
 304         }
 305         return 60;
 306     }
 307 }