Cleanup (unneeded modifiers)
[idea/community.git] / platform / platform-impl / src / com / intellij / util / Restarter.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.util;
17
18 import com.intellij.jna.JnaLoader;
19 import com.intellij.openapi.application.PathManager;
20 import com.intellij.openapi.util.SystemInfo;
21 import com.intellij.openapi.util.io.FileUtilRt;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.sun.jna.Native;
24 import com.sun.jna.Pointer;
25 import com.sun.jna.WString;
26 import com.sun.jna.platform.win32.WinDef;
27 import com.sun.jna.ptr.IntByReference;
28 import com.sun.jna.win32.StdCallLibrary;
29 import org.jetbrains.annotations.NotNull;
30
31 import java.io.BufferedWriter;
32 import java.io.File;
33 import java.io.FileWriter;
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.List;
38
39 public class Restarter {
40   private Restarter() { }
41
42   private static int getRestartCode() {
43     return SystemProperties.getIntProperty("jb.restart.code", 0);
44   }
45
46   public static boolean isSupported() {
47     if (getRestartCode() != 0) {
48       return true;
49     }
50     if (SystemInfo.isWindows) {
51       return JnaLoader.isLoaded() && new File(PathManager.getBinPath(), "restarter.exe").exists();
52     }
53     if (SystemInfo.isMac) {
54       return PathManager.getHomePath().contains(".app");
55     }
56     return false;
57   }
58
59   public static int scheduleRestart(@NotNull String... beforeRestart) throws IOException {
60     try {
61       int restartCode = getRestartCode();
62       if (restartCode != 0) {
63         runCommand(beforeRestart);
64         return restartCode;
65       }
66       else if (SystemInfo.isWindows) {
67         restartOnWindows(beforeRestart);
68         return 0;
69       }
70       else if (SystemInfo.isMac) {
71         restartOnMac(beforeRestart);
72         return 0;
73       }
74     }
75     catch (Throwable t) {
76       throw new IOException("Cannot restart application: " + t.getMessage(), t);
77     }
78
79     runCommand(beforeRestart);
80     throw new IOException("Cannot restart application: not supported.");
81   }
82
83   private static void runCommand(String... beforeRestart) throws IOException {
84     if (beforeRestart.length == 0) return;
85
86     File restartDir = new File(getRestarterDir());
87     String systemPath = new File(System.getProperty("user.home") + "/." + System.getProperty("idea.paths.selector") + "/system/restart").getPath();
88     if (! systemPath.equals(restartDir.getPath())){
89       throw new IOException("idea.system.path was changed. Restart is not supported.");
90     }
91     if (!FileUtilRt.createDirectory(restartDir)) {
92       throw new IOException("Cannot create dir: " + restartDir);
93     }
94
95     File restarter = new File(restartDir, "restarter.sh");
96     try (BufferedWriter output = new BufferedWriter(new FileWriter(restarter))) {
97       output.write("#!/bin/sh\n");
98       for (int i = 0; i < beforeRestart.length; i++) {
99         output.write(beforeRestart[i]);
100         if (i <= beforeRestart.length - 2) output.write(' ');
101         if (i >= beforeRestart.length - 2) output.write('"');
102       }
103       output.write('\n');
104     }
105
106     if (!restarter.setExecutable(true, true)) {
107       throw new IOException("Cannot make file executable: " + restarter);
108     }
109   }
110
111   private static void restartOnWindows(@NotNull String... beforeRestart) throws IOException {
112     Kernel32 kernel32 = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class);
113     Shell32 shell32 = (Shell32)Native.loadLibrary("shell32", Shell32.class);
114
115     int pid = kernel32.GetCurrentProcessId();
116     IntByReference argc = new IntByReference();
117     Pointer argv_ptr = shell32.CommandLineToArgvW(kernel32.GetCommandLineW(), argc);
118     String[] argv = getRestartArgv(argv_ptr.getWideStringArray(0, argc.getValue()));
119     kernel32.LocalFree(argv_ptr);
120
121     // See https://blogs.msdn.microsoft.com/oldnewthing/20060515-07/?p=31203
122     // argv[0] as the program name is only a convention, i.e. there is no guarantee
123     // the name is the full path to the executable.
124     //
125     // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683197(v=vs.85).aspx
126     // To retrieve the full path to the executable, use "GetModuleFileName(NULL, ...)".
127     //
128     // Note: We use 32,767 as buffer size to avoid limiting ourselves to MAX_PATH (260).
129     char[] buffer = new char[32767];
130     if (kernel32.GetModuleFileNameW(null, buffer, new WinDef.DWORD(buffer.length)).intValue() > 0) {
131       argv[0] = Native.toString(buffer);
132     }
133
134     doScheduleRestart(new File(PathManager.getBinPath(), "restarter.exe"), commands -> {
135       Collections.addAll(commands, String.valueOf(pid), String.valueOf(beforeRestart.length));
136       Collections.addAll(commands, beforeRestart);
137       Collections.addAll(commands, String.valueOf(argv.length));
138       Collections.addAll(commands, argv);
139     });
140
141     // Since the process ID is passed through the command line, we want to make sure that we don't exit before the "restarter"
142     // process has a chance to open the handle to our process, and that it doesn't wait for the termination of an unrelated
143     // process which happened to have the same process ID.
144     TimeoutUtil.sleep(500);
145   }
146
147   private static void restartOnMac(@NotNull String... beforeRestart) throws IOException {
148     String homePath = PathManager.getHomePath();
149     int p = homePath.indexOf(".app");
150     if (p < 0) throw new IOException("Application bundle not found: " + homePath);
151
152     String bundlePath = homePath.substring(0, p + 4);
153     doScheduleRestart(new File(PathManager.getBinPath(), "restarter"), commands -> {
154       Collections.addAll(commands, bundlePath);
155       Collections.addAll(commands, beforeRestart);
156     });
157   }
158
159   private static String[] getRestartArgv(String[] argv) {
160     int countArgs = argv.length;
161     for (int i = argv.length-1; i >=0; i--) {
162       if (argv[i].endsWith("com.intellij.idea.Main") ||
163           argv[i].endsWith(".exe")) {
164         countArgs = i + 1;
165         if (argv[i].endsWith(".exe") && argv[i].indexOf(File.separatorChar) < 0) {
166           //absolute path
167           argv[i] = new File(PathManager.getBinPath(), argv[i]).getPath();
168         }
169         break;
170       }
171     }
172     String[] restartArg = new String[countArgs];
173     System.arraycopy(argv, 0, restartArg, 0, countArgs);
174     return restartArg;
175   }
176
177   private static void doScheduleRestart(File restarterFile, Consumer<List<String>> argumentsBuilder) throws IOException {
178     List<String> commands = new ArrayList<>();
179     commands.add(createTempExecutable(restarterFile).getPath());
180     argumentsBuilder.consume(commands);
181     Runtime.getRuntime().exec(ArrayUtil.toStringArray(commands));
182   }
183
184   public static String getRestarterDir() {
185     return PathManager.getSystemPath() + "/restart";
186   }
187
188   public static File createTempExecutable(File executable) throws IOException {
189     File executableDir = new File(getRestarterDir());
190     if (!FileUtilRt.createDirectory(executableDir)) throw new IOException("Cannot create dir: " + executableDir);
191     File copy = new File(executableDir.getPath() + "/" + executable.getName());
192     if (!FileUtilRt.ensureCanCreateFile(copy) || (copy.exists() && !copy.delete())) {
193        String ext = FileUtilRt.getExtension(executable.getName());
194        copy = FileUtilRt.createTempFile(executableDir, FileUtilRt.getNameWithoutExtension(copy.getName()),
195                                         StringUtil.isEmptyOrSpaces(ext) ? ".tmp" : ("." + ext),
196                                         true, false);
197     }
198     FileUtilRt.copy(executable, copy);
199     if (!copy.setExecutable(executable.canExecute())) throw new IOException("Cannot make file executable: " + copy);
200     return copy;
201   }
202
203   private interface Kernel32 extends StdCallLibrary {
204     int GetCurrentProcessId();
205
206     WString GetCommandLineW();
207
208     Pointer LocalFree(Pointer pointer);
209
210     WinDef.DWORD GetModuleFileNameW(WinDef.HMODULE hModule, char[] lpFilename, WinDef.DWORD nSize);
211   }
212
213   private interface Shell32 extends StdCallLibrary {
214     Pointer CommandLineToArgvW(WString command_line, IntByReference argc);
215   }
216 }