Skip to content

Commit

Permalink
Shorten long working directories of launched processes on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
HannesWell committed Jul 30, 2024
1 parent b0c49f9 commit 6de0efd
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 4 deletions.
2 changes: 1 addition & 1 deletion debug/org.eclipse.debug.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Export-Package: org.eclipse.debug.core,
Require-Bundle: org.eclipse.core.resources;bundle-version="[3.18.0,4.0.0)";visibility:=reexport,
org.eclipse.core.variables;bundle-version="[3.2.800,4.0.0)",
org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)",
org.eclipse.core.filesystem;bundle-version="[1.2.0,2.0.0)",
org.eclipse.core.filesystem;bundle-version="[1.11.0,2.0.0)",
org.eclipse.core.expressions;bundle-version="[3.4.0,4.0.0)"
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-17
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,9 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
// builder to not break existing caller of this method
if (mergeOutput) {
ProcessBuilder pb = new ProcessBuilder(cmdLine);
pb.directory(workingDirectory);
if (workingDirectory != null) {
pb.directory(shortenWindowsPath(workingDirectory));
}
pb.redirectErrorStream(mergeOutput);
if (envp != null) {
Map<String, String> env = pb.environment();
Expand All @@ -1006,7 +1008,7 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env
} else if (workingDirectory == null) {
p = Runtime.getRuntime().exec(cmdLine, envp);
} else {
p = Runtime.getRuntime().exec(cmdLine, envp, workingDirectory);
p = Runtime.getRuntime().exec(cmdLine, envp, shortenWindowsPath(workingDirectory));
}
} catch (IOException e) {
Status status = new Status(IStatus.ERROR, getUniqueIdentifier(), ERROR, DebugCoreMessages.DebugPlugin_0, e);
Expand All @@ -1018,14 +1020,36 @@ public static Process exec(String[] cmdLine, File workingDirectory, String[] env

if (handler != null) {
Object result = handler.handleStatus(status, null);
if (result instanceof Boolean && ((Boolean) result).booleanValue()) {
if (result instanceof Boolean resultValue && resultValue) {
p = exec(cmdLine, null);
}
}
}
return p;
}

// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
private static final int WINDOWS_MAX_PATH = 258;

private static File shortenWindowsPath(File path) {
if (path.getPath().length() > WINDOWS_MAX_PATH && Platform.OS.isWindows()) {
// When spawning new processes on Windows, there is no uniform way
// to use long working directory paths that exceed the default path
// length limit, like for example using the raw path prefix '\\?\'
// See https://bugs.openjdk.org/browse/JDK-8315405
// The best we can do is trying to shorten the path and hope that
// it becomes sufficiently short.
@SuppressWarnings("restriction")
String shortPath = org.eclipse.core.internal.filesystem.local.Win32Handler.getShortPathName(path.toString());
if (shortPath != null) {
return new File(shortPath);
} else {
log(Status.warning("Working directory of process to create exceeds Window's MAX_PATH limit and shortening the path failed.")); //$NON-NLS-1$
}
}
return path;
}

/**
* Returns whether this plug-in is in the process of
* being shutdown.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,38 @@
*******************************************************************************/
package org.eclipse.debug.tests.launching;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.Launch;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IDisconnect;
import org.eclipse.debug.core.model.IProcess;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

/**
* Tests for the {@link Launch} class
Expand Down Expand Up @@ -124,6 +136,30 @@ public void testDisconnectedAndWriteProcesses() throws Exception {
assertTrue(testExecution(readIsDisconnectedTask, writeProcessesTask));
}

@ClassRule
public static TemporaryFolder tempFolder = new TemporaryFolder();

@Test
public void testProcessLaunchWithLongWorkingDirectory() throws CoreException, IOException {
int rootLength = tempFolder.getRoot().toString().length();
String subPathElementsName = "subfolder-with-relativly-long-name";
String[] segments = Collections.nCopies((400 - rootLength) / subPathElementsName.length(), subPathElementsName).toArray(String[]::new);
File workingDirectory = tempFolder.newFolder(segments);
assertTrue(workingDirectory.toString().length() > 300);

startProcessAndAssertOutputContains(List.of("java", "--version"), workingDirectory, false, "jdk");
startProcessAndAssertOutputContains(List.of("java", "--version"), workingDirectory, true, "jdk");
}

private static void startProcessAndAssertOutputContains(List<String> cmdLine, File workingDirectory, boolean mergeOutput, String expectedOutput) throws CoreException, IOException {
Process process = DebugPlugin.exec(cmdLine.toArray(String[]::new), workingDirectory, null, mergeOutput);
String output;
try (BufferedReader outputReader = new BufferedReader(process.inputReader())) {
output = outputReader.lines().collect(Collectors.joining());
}
assertThat(output.toLowerCase(Locale.ENGLISH)).contains(expectedOutput);
}

private boolean testExecution(final Runnable readTask, final Runnable writeTask) {
/*
* Normally 10 times trial is sufficient to reproduce concurrent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ public boolean putFileInfo(String fileName, IFileInfo info, int options) {
return FileAPIh.SetFileAttributesW(lpFileName, fileAttributes);
}

public static String getShortPathName(String longPath) {
longPath = toLongWindowsPath(longPath);
char[] buffer = new char[longPath.length()];
// https://learn.microsoft.com/de-de/windows/win32/api/fileapi/nf-fileapi-getshortpathnamew
int newLength = com.sun.jna.platform.win32.Kernel32.INSTANCE.GetShortPathName(longPath, buffer, buffer.length);
if (0 < newLength && newLength < buffer.length) { // zero means error
int offset = longPath.startsWith(WIN32_UNC_RAW_PATH_PREFIX) ? WIN32_UNC_RAW_PATH_PREFIX.length() : WIN32_RAW_PATH_PREFIX.length();
return new String(buffer, offset, newLength);
}
return null;
}

private static String toLongWindowsPath(String fileName) {
// See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
if (fileName.startsWith("\\\\") && !fileName.startsWith(WIN32_UNC_RAW_PATH_PREFIX)) { //$NON-NLS-1$
Expand Down

0 comments on commit 6de0efd

Please sign in to comment.