Merge "Merge branch 'stable-2.13'"
diff --git a/Documentation/install.txt b/Documentation/install.txt
index 44f6189..86c9f9a 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -172,6 +172,50 @@
 the embedded Jetty server, see
 link:install-j2ee.html[J2EE installation].
 
+[[installation_on_windows]]
+== Installation on Windows
+
+If new site is going to be initialized with Bouncy Castle cryptography,
+ssh-keygen command must be available during the init phase. If you have
+link:https://git-for-windows.github.io/[Git for Windows] installed,
+start Command Prompt and temporary add directory with ssh-keygen to the
+PATH environment variable just before running init command:
+
+====
+  PATH=%PATH%;c:\Program Files\Git\usr\bin
+====
+
+Please note that the path in the above example must not be
+double-quoted.
+
+To run the daemon after site initialization execute:
+
+====
+  cd C:\MY\GERRIT\SITE
+  java.exe -jar bin\gerrit.war daemon --console-log
+====
+
+To stop the daemon press Ctrl+C.
+
+=== Install the daemon as Windows Service
+
+To install Gerrit as Windows Service use the
+link:http://commons.apache.org/proper/commons-daemon/procrun.html[Apache
+Commons Daemon Procrun].
+
+Sample install command:
+
+====
+  prunsrv.exe //IS//Gerrit --DisplayName="Gerrit Code Review" --Startup=auto ^
+        --Jvm="C:\Program Files\Java\jre1.8.0_65\bin\server\jvm.dll" ^
+        --Classpath=C:\MY\GERRIT\SITE\bin\gerrit.war ^
+        --LogPath=C:\MY\GERRIT\SITE\logs ^
+        --StartPath=C:\MY\GERRIT\SITE ^
+        --StartMode=jvm --StopMode=jvm ^
+        --StartClass=com.google.gerrit.launcher.GerritLauncher --StartMethod=daemonStart ^
+        --StopClass=com.google.gerrit.launcher.GerritLauncher --StopMethod=daemonStop ^
+        ++DependsOn=postgresql-x64-9.4
+====
 
 [[customize]]
 == Site Customization
diff --git a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
index bef57d0..a272864 100644
--- a/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/gerrit-launcher/src/main/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -55,6 +55,8 @@
   private static final String pkg = "com.google.gerrit.pgm";
   public static final String NOT_ARCHIVED = "NOT_ARCHIVED";
 
+  private static ClassLoader daemonClassLoader;
+
   public static void main(final String[] argv) throws Exception {
     System.exit(mainImpl(argv));
   }
@@ -102,6 +104,44 @@
     return invokeProgram(cl, argv);
   }
 
+  public static void daemonStart(final String[] argv) throws Exception {
+    if (daemonClassLoader != null) {
+      throw new IllegalStateException(
+        "daemonStart can be called only once per JVM instance");
+    }
+    final ClassLoader cl = libClassLoader(false);
+    Thread.currentThread().setContextClassLoader(cl);
+
+    daemonClassLoader = cl;
+
+    String[] daemonArgv = new String[argv.length + 1];
+    daemonArgv[0] = "daemon";
+    for (int i = 0; i < argv.length; i++) {
+      daemonArgv[i + 1] = argv[i];
+    }
+    int res = invokeProgram(cl, daemonArgv);
+    if (res != 0) {
+      throw new Exception("Unexpected return value: " + res);
+    }
+  }
+
+  public static void daemonStop(final String[] argv) throws Exception {
+    if (daemonClassLoader == null) {
+      throw new IllegalStateException(
+        "daemonStop can be called only after call to daemonStop");
+    }
+    String[] daemonArgv = new String[argv.length + 2];
+    daemonArgv[0] = "daemon";
+    daemonArgv[1] = "--stop-only";
+    for (int i = 0; i < argv.length; i++) {
+      daemonArgv[i + 2] = argv[i];
+    }
+    int res = invokeProgram(daemonClassLoader, daemonArgv);
+    if (res != 0) {
+      throw new Exception("Unexpected return value: " + res);
+    }
+  }
+
   private static boolean isProlog(String cn) {
     return "PrologShell".equals(cn) || "Rulec".equals(cn);
   }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index e7150dd..eb17530 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -125,7 +125,7 @@
   private boolean sshd = true;
 
   @Option(name = "--disable-sshd", usage = "Disable the internal SSH daemon")
-  void setDisableSshd(@SuppressWarnings("unused")  boolean arg) {
+  void setDisableSshd(@SuppressWarnings("unused") boolean arg) {
     sshd = false;
   }
 
@@ -151,6 +151,9 @@
       usage = "Init site before starting the daemon")
   private boolean doInit;
 
+  @Option(name = "--stop-only", usage = "Stop the daemon", hidden = true)
+  private boolean stopOnly;
+
   private final LifecycleManager manager = new LifecycleManager();
   private Injector dbInjector;
   private Injector cfgInjector;
@@ -181,6 +184,10 @@
 
   @Override
   public int run() throws Exception {
+    if (stopOnly) {
+      RuntimeShutdown.manualShutdown();
+      return 0;
+    }
     if (doInit) {
       try {
         new Init(getSitePath()).run();
@@ -214,14 +221,7 @@
         @Override
         public void run() {
           log.info("caught shutdown, cleaning up");
-          if (runId != null) {
-            try {
-              Files.delete(runFile);
-            } catch (IOException err) {
-              log.warn("failed to delete " + runFile, err);
-            }
-          }
-          manager.stop();
+          stop();
         }
       });
 
@@ -313,6 +313,13 @@
 
   @VisibleForTesting
   public void stop() {
+    if (runId != null) {
+      try {
+        Files.delete(runFile);
+      } catch (IOException err) {
+        log.warn("failed to delete " + runFile, err);
+      }
+    }
     manager.stop();
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index 63d4883..904d4f2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.pgm.init.api.InitStep;
 import com.google.gerrit.pgm.init.api.Section;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.util.HostPlatform;
 import com.google.gerrit.server.util.SocketUtil;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -31,6 +32,7 @@
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 
 import java.io.IOException;
+import java.lang.ProcessBuilder.Redirect;
 import java.net.InetSocketAddress;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -103,25 +105,30 @@
         //
         final String comment = "gerrit-code-review@" + hostname();
 
+        // Workaround for JDK-6518827 - zero-length argument ignored on Win32
+        String emptyPassphraseArg = HostPlatform.isWin32() ? "\"\"" : "";
+
         System.err.print(" rsa...");
         System.err.flush();
-        Runtime.getRuntime().exec(new String[] {"ssh-keygen",
+        new ProcessBuilder("ssh-keygen",
             "-q" /* quiet */,
             "-t", "rsa",
-            "-P", "",
+            "-P", emptyPassphraseArg,
             "-C", comment,
-            "-f", site.ssh_rsa.toAbsolutePath().toString(),
-            }).waitFor();
+            "-f", site.ssh_rsa.toAbsolutePath().toString()
+        ).redirectError(Redirect.INHERIT).redirectOutput(Redirect.INHERIT)
+            .start().waitFor();
 
         System.err.print(" dsa...");
         System.err.flush();
-        Runtime.getRuntime().exec(new String[] {"ssh-keygen",
+        new ProcessBuilder("ssh-keygen",
             "-q" /* quiet */,
             "-t", "dsa",
-            "-P", "",
+            "-P", emptyPassphraseArg,
             "-C", comment,
-            "-f", site.ssh_dsa.toAbsolutePath().toString(),
-            }).waitFor();
+            "-f", site.ssh_dsa.toAbsolutePath().toString()
+        ).redirectError(Redirect.INHERIT).redirectOutput(Redirect.INHERIT)
+            .start().waitFor();
 
       } else {
         // Generate the SSH daemon host key ourselves. This is complex
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/RuntimeShutdown.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/RuntimeShutdown.java
index dc3a915..86fef21 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/RuntimeShutdown.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/RuntimeShutdown.java
@@ -39,6 +39,10 @@
     cb.waitForShutdown();
   }
 
+  public static void manualShutdown() {
+    cb.manualShutdown();
+  }
+
   private RuntimeShutdown() {
   }
 
@@ -96,6 +100,11 @@
       }
     }
 
+    void manualShutdown() {
+      Runtime.getRuntime().removeShutdownHook(this);
+      run();
+    }
+
     void waitForShutdown() {
       synchronized (this) {
         while (!shutdownComplete) {