Get short path name for native controllers

Due to limitations with CreateProcessW on Windows (ultimately used by
ProcessBuilder) with respect to maximum path lengths, we need to get the
short path name for any native controllers before trying to start them
in case the absolute path exceeds the maximum path length. This commit
uses JNA to invoke the necessary Windows API for this to start the
native controller using the short path.

To be precise about the limitation here, the MSDN docs for
CreateProcessW say for the command line parameter:

>The command line to be executed. The maximum length of this string is
>32,768 characters, including the Unicode terminating null character. If
>lpApplicationName is NULL, the module name portionof lpCommandLine is
>limited to MAX_PATH characters.

This is exactly how the Windows implementation of Process in the JDK
invokes CreateProcessW: with the executable name (lpApplicationName) set
to NULL.

Relates #25344
This commit is contained in:
Jason Tedor 2017-06-22 07:59:58 -04:00 committed by GitHub
parent e41eae9f05
commit 97a2c4523d
4 changed files with 73 additions and 1 deletions

View File

@ -24,6 +24,7 @@ import com.sun.jna.Native;
import com.sun.jna.NativeLong; import com.sun.jna.NativeLong;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import com.sun.jna.Structure; import com.sun.jna.Structure;
import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary; import com.sun.jna.win32.StdCallLibrary;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.Constants; import org.apache.lucene.util.Constants;
@ -223,6 +224,17 @@ final class JNAKernel32Library {
*/ */
native boolean CloseHandle(Pointer handle); native boolean CloseHandle(Pointer handle);
/**
* Retrieves the short path form of the specified path. See
* <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989.aspx">{@code GetShortPathName}</a>.
*
* @param lpszLongPath the path string
* @param lpszShortPath a buffer to receive the short name
* @param cchBuffer the size of the buffer
* @return the length of the string copied into {@code lpszShortPath}, otherwise zero for failure
*/
native int GetShortPathNameW(WString lpszLongPath, char[] lpszShortPath, int cchBuffer);
/** /**
* Creates or opens a new job object * Creates or opens a new job object
* *

View File

@ -21,6 +21,7 @@ package org.elasticsearch.bootstrap;
import com.sun.jna.Native; import com.sun.jna.Native;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import com.sun.jna.WString;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.Constants; import org.apache.lucene.util.Constants;
import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.logging.Loggers;
@ -194,6 +195,35 @@ class JNANatives {
} }
} }
/**
* Retrieves the short path form of the specified path.
*
* @param path the path
* @return the short path name (or the original path if getting the short path name fails for any reason)
*/
static String getShortPathName(String path) {
assert Constants.WINDOWS;
try {
final WString longPath = new WString("\\\\?\\" + path);
// first we get the length of the buffer needed
final int length = JNAKernel32Library.getInstance().GetShortPathNameW(longPath, null, 0);
if (length == 0) {
logger.warn("failed to get short path name: {}", Native.getLastError());
return path;
}
final char[] shortPath = new char[length];
// knowing the length of the buffer, now we get the short name
if (JNAKernel32Library.getInstance().GetShortPathNameW(longPath, shortPath, length) > 0) {
return Native.toString(shortPath);
} else {
logger.warn("failed to get short path name: {}", Native.getLastError());
return path;
}
} catch (final UnsatisfiedLinkError e) {
return path;
}
}
static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) { static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
// The console Ctrl handler is necessary on Windows platforms only. // The console Ctrl handler is necessary on Windows platforms only.
if (Constants.WINDOWS) { if (Constants.WINDOWS) {

View File

@ -76,6 +76,20 @@ final class Natives {
JNANatives.tryVirtualLock(); JNANatives.tryVirtualLock();
} }
/**
* Retrieves the short path form of the specified path.
*
* @param path the path
* @return the short path name (or the original path if getting the short path name fails for any reason)
*/
static String getShortPathName(final String path) {
if (!JNA_AVAILABLE) {
logger.warn("cannot obtain short path for [{}] because JNA is not avilable", path);
return path;
}
return JNANatives.getShortPathName(path);
}
static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) { static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
if (!JNA_AVAILABLE) { if (!JNA_AVAILABLE) {
logger.warn("cannot register console handler because JNA is not available"); logger.warn("cannot register console handler because JNA is not available");

View File

@ -19,6 +19,7 @@
package org.elasticsearch.bootstrap; package org.elasticsearch.bootstrap;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IOUtils;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.plugins.Platforms; import org.elasticsearch.plugins.Platforms;
@ -99,7 +100,22 @@ final class Spawner implements Closeable {
private Process spawnNativePluginController( private Process spawnNativePluginController(
final Path spawnPath, final Path spawnPath,
final Path tmpPath) throws IOException { final Path tmpPath) throws IOException {
final ProcessBuilder pb = new ProcessBuilder(spawnPath.toString()); final String command;
if (Constants.WINDOWS) {
/*
* We have to get the short path name or starting the process could fail due to max path limitations. The underlying issue here
* is that starting the process on Windows ultimately involves the use of CreateProcessW. CreateProcessW has a limitation that
* if its first argument (the application name) is null, then its second argument (the command line for the process to start) is
* restricted in length to 260 characters (cf. https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425.aspx). Since
* this is exactly how the JDK starts the process on Windows (cf.
* http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/windows/native/java/lang/ProcessImpl_md.c#l319), this
* limitation is in force. As such, we use the short name to avoid any such problems.
*/
command = Natives.getShortPathName(spawnPath.toString());
} else {
command = spawnPath.toString();
}
final ProcessBuilder pb = new ProcessBuilder(command);
// the only environment variable passes on the path to the temporary directory // the only environment variable passes on the path to the temporary directory
pb.environment().clear(); pb.environment().clear();