d0208788630a2782e1c8b4f238ba3ad9a734859e
[idea/community.git] / platform / platform-impl / src / com / intellij / execution / wsl / WSLUtil.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.execution.wsl;
3
4 import com.intellij.execution.ExecutionException;
5 import com.intellij.execution.configurations.GeneralCommandLine;
6 import com.intellij.execution.process.*;
7 import com.intellij.execution.util.ExecUtil;
8 import com.intellij.openapi.application.Experiments;
9 import com.intellij.openapi.diagnostic.Logger;
10 import com.intellij.openapi.util.NlsSafe;
11 import com.intellij.openapi.util.SystemInfo;
12 import com.intellij.openapi.util.io.FileUtil;
13 import com.intellij.openapi.util.text.StringUtil;
14 import com.intellij.util.ThreeState;
15 import com.intellij.util.containers.ContainerUtil;
16 import org.jetbrains.annotations.ApiStatus;
17 import org.jetbrains.annotations.NotNull;
18 import org.jetbrains.annotations.Nullable;
19
20 import java.nio.charset.StandardCharsets;
21 import java.nio.file.Files;
22 import java.nio.file.LinkOption;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.List;
29
30 /**
31  * Class for working with WSL after Fall Creators Update
32  * https://blogs.msdn.microsoft.com/commandline/2017/10/11/whats-new-in-wsl-in-windows-10-fall-creators-update/
33  * - multiple linuxes
34  * - file system is unavailable form windows (for now at least)
35  */
36 public final class WSLUtil {
37   public static final Logger LOG = Logger.getInstance("#com.intellij.execution.wsl");
38
39   /**
40    * @deprecated use {@link WslDistributionManager#getInstalledDistributions} instead.
41    * Alternatively, check {@link WSLUtil#isSystemCompatible} and show standard WSL UI, e.g.
42    * {@link com.intellij.execution.wsl.ui.WslDistributionComboBox}. If no WSL distributions installed,
43    * it will show "No installed distributions" message.
44    */
45   @Deprecated
46   public static boolean hasAvailableDistributions() {
47     return !getAvailableDistributions().isEmpty();
48   }
49
50
51   /**
52    * @deprecated use {@link WslDistributionManager#getInstalledDistributions()} instead
53    */
54   @Deprecated
55   @ApiStatus.ScheduledForRemoval(inVersion = "2021.3")
56   @NotNull
57   public static List<WSLDistribution> getAvailableDistributions() {
58     if (!isSystemCompatible()) return Collections.emptyList();
59
60     final Path executableRoot = getExecutableRootPath();
61     if (executableRoot == null) return Collections.emptyList();
62
63     Collection<WslDistributionDescriptor> descriptors = WSLDistributionService.getInstance().getDescriptors();
64     final List<WSLDistribution> result = new ArrayList<>(descriptors.size() + 1 /* LEGACY_WSL */);
65
66     for (WslDistributionDescriptor descriptor: descriptors) {
67       String executablePathStr = descriptor.getExecutablePath();
68       if (executablePathStr != null) {
69         Path executablePath = Paths.get(executablePathStr);
70         if (!executablePath.isAbsolute()) {
71           executablePath = executableRoot.resolve(executablePath);
72         }
73
74         if (Files.exists(executablePath, LinkOption.NOFOLLOW_LINKS)) {
75           result.add(new WSLDistribution(descriptor, executablePath));
76         }
77       }
78     }
79
80     // add legacy WSL if it's available and enabled
81     if (Experiments.getInstance().isFeatureEnabled("wsl.legacy.distribution")) {
82       ContainerUtil.addIfNotNull(result, WSLDistributionLegacy.getInstance());
83     }
84
85     return result;
86   }
87
88   /**
89    * @return root for WSL executable or null if unavailable
90    */
91   @Nullable
92   private static Path getExecutableRootPath() {
93     String localAppDataPath = System.getenv().get("LOCALAPPDATA");
94     return StringUtil.isEmpty(localAppDataPath) ? null : Paths.get(localAppDataPath, "Microsoft\\WindowsApps");
95   }
96
97   /**
98    * @deprecated use {@link WslDistributionManager#getOrCreateDistributionByMsId(String)} instead
99    */
100   @Deprecated
101   @ApiStatus.ScheduledForRemoval(inVersion = "2021.3")
102   @Nullable
103   public static WSLDistribution getDistributionById(@Nullable String id) {
104     if (id == null) {
105       return null;
106     }
107     for (WSLDistribution distribution : getAvailableDistributions()) {
108       if (id.equals(distribution.getId())) {
109         return distribution;
110       }
111     }
112     return null;
113   }
114
115   /**
116    * @return instance of WSL distribution or null if it's unavailable
117    * @deprecated Use {@link WslDistributionManager#getOrCreateDistributionByMsId(String)}
118    */
119   @Nullable
120   @Deprecated
121   public static WSLDistribution getDistributionByMsId(@Nullable String name) {
122     if (name == null) {
123       return null;
124     }
125     for (WSLDistribution distribution : getAvailableDistributions()) {
126       if (name.equals(distribution.getMsId())) {
127         return distribution;
128       }
129     }
130     return null;
131   }
132
133   public static boolean isSystemCompatible() {
134     return SystemInfo.isWin10OrNewer;
135   }
136
137   /**
138    * @param wslPath a path in WSL file system, e.g. "/mnt/c/Users/file.txt" or "/c/Users/file.txt"
139    * @param mntRoot a directory where fixed drives will be mounted. Default is "/mnt/" - {@link WSLDistribution#DEFAULT_WSL_MNT_ROOT}).
140    *               See https://docs.microsoft.com/ru-ru/windows/wsl/wsl-config#configuration-options
141    * @return Windows-dependent path to the file, pointed by {@code wslPath} in WSL or null if the path is unmappable.
142    * For example, {@code getWindowsPath("/mnt/c/Users/file.txt", "/mnt/") returns "C:\Users\file.txt"}
143    */
144   @Nullable
145   public static String getWindowsPath(@NotNull String wslPath, @NotNull String mntRoot) {
146     if (!wslPath.startsWith(mntRoot)) {
147       return null;
148     }
149     int driveLetterIndex = mntRoot.length();
150     if (driveLetterIndex >= wslPath.length() || !Character.isLetter(wslPath.charAt(driveLetterIndex))) {
151       return null;
152     }
153     int slashIndex = driveLetterIndex + 1;
154     if (slashIndex < wslPath.length() && wslPath.charAt(slashIndex) != '/') {
155       return null;
156     }
157     return FileUtil.toSystemDependentName(Character.toUpperCase(wslPath.charAt(driveLetterIndex)) + ":" + wslPath.substring(slashIndex));
158   }
159
160   @NotNull
161   public static ThreeState isWsl1(@NotNull WSLDistribution distribution) {
162     try {
163       ProcessOutput output = distribution.executeOnWsl(10_000, "uname", "-v");
164       if (output.getExitCode() != 0) return ThreeState.UNSURE;
165       return ThreeState.fromBoolean(output.getStdout().contains("Microsoft"));
166     }
167     catch (ExecutionException e) {
168       LOG.warn(e);
169       return ThreeState.UNSURE;
170     }
171   }
172
173   /**
174    * @param distribution
175    * @return version if it can be determined or -1 instead
176    */
177   public static int getWslVersion(@NotNull WSLDistribution distribution) {
178     Path wslExe = WSLDistribution.findWslExe();
179     if (wslExe == null) {
180       LOG.warn("wsl.exe is not found");
181       return -1;
182     }
183
184     GeneralCommandLine commandLine = new GeneralCommandLine(wslExe.toString(), "-l", "-v").withCharset(StandardCharsets.UTF_16LE);
185
186     ProcessOutput output;
187     try {
188       output = ExecUtil.execAndGetOutput(commandLine, 10_000);
189     }
190     catch (ExecutionException e) {
191       LOG.warn("Failed to run " + commandLine.getCommandLineString(), e);
192       return -1;
193     }
194     if (output.isTimeout() || output.getExitCode() != 0 || !output.getStderr().isEmpty()) {
195       String details = StringUtil.join(ContainerUtil.newArrayList(
196         "timeout: " + output.isTimeout(),
197         "exitCode: " + output.getExitCode(),
198         "stdout: " + output.getStdout(),
199         "stderr: " + output.getStderr()
200       ), ", ");
201       LOG.warn("Failed to run " + commandLine.getCommandLineString() + ": " + details);
202       return -1;
203     }
204
205     List<@NlsSafe String> lines = output.getStdoutLines();
206     return lines.stream().skip(1).mapToInt(l -> {
207       List<String> words = StringUtil.split(l, " ");
208
209       int size = words.size();
210       if (size >= 3 && distribution.getMsId().equals(words.get(size - 3))) {
211         return StringUtil.parseInt(words.get(size - 1), -1);
212       }
213       return -1;
214     }).filter(v -> v != -1).findFirst().orElse(-1);
215   }
216
217   public static @NotNull @NlsSafe String getMsId(@NotNull @NlsSafe String msOrInternalId) {
218     WslDistributionDescriptor descriptor = ContainerUtil.find(WSLDistributionService.getInstance().getDescriptors(),
219                                                               d -> d.getId().equals(msOrInternalId));
220     return descriptor != null ? descriptor.getMsId() : msOrInternalId;
221   }
222 }