Skip to content

Commit 6de0efd

Browse files
committed
Shorten long working directories of launched processes on Windows
Fixes eclipse-pde/eclipse.pde#1333
1 parent b0c49f9 commit 6de0efd

File tree

4 files changed

+76
-4
lines changed

4 files changed

+76
-4
lines changed

debug/org.eclipse.debug.core/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Export-Package: org.eclipse.debug.core,
2121
Require-Bundle: org.eclipse.core.resources;bundle-version="[3.18.0,4.0.0)";visibility:=reexport,
2222
org.eclipse.core.variables;bundle-version="[3.2.800,4.0.0)",
2323
org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)",
24-
org.eclipse.core.filesystem;bundle-version="[1.2.0,2.0.0)",
24+
org.eclipse.core.filesystem;bundle-version="[1.11.0,2.0.0)",
2525
org.eclipse.core.expressions;bundle-version="[3.4.0,4.0.0)"
2626
Bundle-ActivationPolicy: lazy
2727
Bundle-RequiredExecutionEnvironment: JavaSE-17

debug/org.eclipse.debug.core/core/org/eclipse/debug/core/DebugPlugin.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,9 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
990990
// builder to not break existing caller of this method
991991
if (mergeOutput) {
992992
ProcessBuilder pb = new ProcessBuilder(cmdLine);
993-
pb.directory(workingDirectory);
993+
if (workingDirectory != null) {
994+
pb.directory(shortenWindowsPath(workingDirectory));
995+
}
994996
pb.redirectErrorStream(mergeOutput);
995997
if (envp != null) {
996998
Map<String, String> env = pb.environment();
@@ -1006,7 +1008,7 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
10061008
} else if (workingDirectory == null) {
10071009
p = Runtime.getRuntime().exec(cmdLine, envp);
10081010
} else {
1009-
p = Runtime.getRuntime().exec(cmdLine, envp, workingDirectory);
1011+
p = Runtime.getRuntime().exec(cmdLine, envp, shortenWindowsPath(workingDirectory));
10101012
}
10111013
} catch (IOException e) {
10121014
Status status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_0, e);
@@ -1018,14 +1020,36 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
10181020

10191021
if (handler != null) {
10201022
Object result = handler.handleStatus(status, null);
1021-
if (result instanceof Boolean && ((Boolean) result).booleanValue()) {
1023+
if (result instanceof Boolean resultValue && resultValue) {
10221024
p = exec(cmdLine, null);
10231025
}
10241026
}
10251027
}
10261028
return p;
10271029
}
10281030

1031+
// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
1032+
private static final int WINDOWS_MAX_PATH = 258;
1033+
1034+
private static File shortenWindowsPath(File path) {
1035+
if (path.getPath().length() > WINDOWS_MAX_PATH && Platform.OS.isWindows()) {
1036+
// When spawning new processes on Windows, there is no uniform way
1037+
// to use long working directory paths that exceed the default path
1038+
// length limit, like for example using the raw path prefix '\\?\'
1039+
// See https://bugs.openjdk.org/browse/JDK-8315405
1040+
// The best we can do is trying to shorten the path and hope that
1041+
// it becomes sufficiently short.
1042+
@SuppressWarnings("restriction")
1043+
String shortPath = org.eclipse.core.internal.filesystem.local.Win32Handler.getShortPathName(path.toString());
1044+
if (shortPath != null) {
1045+
return new File(shortPath);
1046+
} else {
1047+
log(Status.warning("Working directory of process to create exceeds Window's MAX_PATH limit and shortening the path failed.")); //$NON-NLS-1$
1048+
}
1049+
}
1050+
return path;
1051+
}
1052+
10291053
/**
10301054
* Returns whether this plug-in is in the process of
10311055
* being shutdown.

debug/org.eclipse.debug.tests/src/org/eclipse/debug/tests/launching/LaunchTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,38 @@
1313
*******************************************************************************/
1414
package org.eclipse.debug.tests.launching;
1515

16+
import static org.assertj.core.api.Assertions.assertThat;
1617
import static org.junit.Assert.assertEquals;
1718
import static org.junit.Assert.assertTrue;
1819

20+
import java.io.BufferedReader;
21+
import java.io.File;
22+
import java.io.IOException;
1923
import java.lang.reflect.InvocationHandler;
2024
import java.lang.reflect.Proxy;
25+
import java.util.Collections;
2126
import java.util.ConcurrentModificationException;
27+
import java.util.List;
28+
import java.util.Locale;
2229
import java.util.concurrent.Semaphore;
2330
import java.util.concurrent.atomic.AtomicInteger;
31+
import java.util.stream.Collectors;
2432

33+
import org.eclipse.core.runtime.CoreException;
2534
import org.eclipse.core.runtime.IProgressMonitor;
2635
import org.eclipse.core.runtime.IStatus;
2736
import org.eclipse.core.runtime.Status;
2837
import org.eclipse.core.runtime.jobs.Job;
38+
import org.eclipse.debug.core.DebugPlugin;
2939
import org.eclipse.debug.core.ILaunchManager;
3040
import org.eclipse.debug.core.Launch;
3141
import org.eclipse.debug.core.model.IDebugTarget;
3242
import org.eclipse.debug.core.model.IDisconnect;
3343
import org.eclipse.debug.core.model.IProcess;
3444
import org.junit.Before;
45+
import org.junit.ClassRule;
3546
import org.junit.Test;
47+
import org.junit.rules.TemporaryFolder;
3648

3749
/**
3850
* Tests for the {@link Launch} class
@@ -124,6 +136,30 @@ public void testDisconnectedAndWriteProcesses() throws Exception {
124136
assertTrue(testExecution(readIsDisconnectedTask, writeProcessesTask));
125137
}
126138

139+
@ClassRule
140+
public static TemporaryFolder tempFolder = new TemporaryFolder();
141+
142+
@Test
143+
public void testProcessLaunchWithLongWorkingDirectory() throws CoreException, IOException {
144+
int rootLength = tempFolder.getRoot().toString().length();
145+
String subPathElementsName = "subfolder-with-relativly-long-name";
146+
String[] segments = Collections.nCopies((400 - rootLength) / subPathElementsName.length(), subPathElementsName).toArray(String[]::new);
147+
File workingDirectory = tempFolder.newFolder(segments);
148+
assertTrue(workingDirectory.toString().length() > 300);
149+
150+
startProcessAndAssertOutputContains(List.of("java", "--version"), workingDirectory, false, "jdk");
151+
startProcessAndAssertOutputContains(List.of("java", "--version"), workingDirectory, true, "jdk");
152+
}
153+
154+
private static void startProcessAndAssertOutputContains(List<String> cmdLine, File workingDirectory, boolean mergeOutput, String expectedOutput) throws CoreException, IOException {
155+
Process process = DebugPlugin.exec(cmdLine.toArray(String[]::new), workingDirectory, null, mergeOutput);
156+
String output;
157+
try (BufferedReader outputReader = new BufferedReader(process.inputReader())) {
158+
output = outputReader.lines().collect(Collectors.joining());
159+
}
160+
assertThat(output.toLowerCase(Locale.ENGLISH)).contains(expectedOutput);
161+
}
162+
127163
private boolean testExecution(final Runnable readTask, final Runnable writeTask) {
128164
/*
129165
* Normally 10 times trial is sufficient to reproduce concurrent

resources/bundles/org.eclipse.core.filesystem/src/org/eclipse/core/internal/filesystem/local/Win32Handler.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ public boolean putFileInfo(String fileName, IFileInfo info, int options) {
111111
return FileAPIh.SetFileAttributesW(lpFileName, fileAttributes);
112112
}
113113

114+
public static String getShortPathName(String longPath) {
115+
longPath = toLongWindowsPath(longPath);
116+
char[] buffer = new char[longPath.length()];
117+
// https://learn.microsoft.com/de-de/windows/win32/api/fileapi/nf-fileapi-getshortpathnamew
118+
int newLength = com.sun.jna.platform.win32.Kernel32.INSTANCE.GetShortPathName(longPath, buffer, buffer.length);
119+
if (0 < newLength && newLength < buffer.length) { // zero means error
120+
int offset = longPath.startsWith(WIN32_UNC_RAW_PATH_PREFIX) ? WIN32_UNC_RAW_PATH_PREFIX.length() : WIN32_RAW_PATH_PREFIX.length();
121+
return new String(buffer, offset, newLength);
122+
}
123+
return null;
124+
}
125+
114126
private static String toLongWindowsPath(String fileName) {
115127
// See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
116128
if (fileName.startsWith("\\\\") && !fileName.startsWith(WIN32_UNC_RAW_PATH_PREFIX)) { //$NON-NLS-1$

0 commit comments

Comments
 (0)