File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\lifecycle\WindowsInstallerLink.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Seiji Sogabe, CloudBees, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.lifecycle;
25
26 import com.sun.jna.Native;
27 import hudson.Functions;
28 import hudson.Launcher.LocalLauncher;
29 import hudson.model.ManagementLink;
30 import hudson.model.TaskListener;
31 import hudson.util.jna.Kernel32Utils;
32 import hudson.util.jna.SHELLEXECUTEINFO;
33 import hudson.util.jna.Shell32;
34 import jenkins.model.Jenkins;
35 import hudson.AbortException;
36 import hudson.Extension;
37 import hudson.util.StreamTaskListener;
38 import hudson.util.jna.DotNet;
39 import org.apache.commons.io.IOUtils;
40 import org.kohsuke.stapler.QueryParameter;
41 import org.kohsuke.stapler.StaplerRequest;
42 import org.kohsuke.stapler.StaplerResponse;
43 import org.apache.commons.io.FileUtils;
44 import org.apache.commons.io.output.ByteArrayOutputStream;
45 import org.apache.tools.ant.taskdefs.Move;
46 import org.apache.tools.ant.Project;
47 import org.apache.tools.ant.DefaultLogger;
48 import org.apache.tools.ant.types.FileSet;
49
50 import javax.servlet.ServletException;
51 import java.io.File;
52 import java.io.FileInputStream;
53 import java.io.IOException;
54 import java.util.logging.Logger;
55 import java.util.logging.Level;
56 import java.net.URL;
57
58 import static hudson.util.jna.SHELLEXECUTEINFO.*;
59
60 /**
61 * {@link ManagementLink} that allows the installation as a Windows service.
62 *
63 * @author Kohsuke Kawaguchi
64 */
65 public class WindowsInstallerLink extends ManagementLink {
66
67 /**
68 * Location of the jenkins.war.
69 * In general case, we can't determine this value, yet having this is a requirement for the installer.
70 */
71 private final File hudsonWar;
72
73 /**
74 * If the installation is completed, this value holds the installation directory.
75 */
76 private volatile File installationDir;
77
78 private WindowsInstallerLink(File jenkinsWar) {
79 this.hudsonWar = jenkinsWar;
80 }
81
82 public String getIconFileName() {
83 return "installer.gif";
84 }
85
86 public String getUrlName() {
87 return "install";
88 }
89
90 public String getDisplayName() {
91 return Messages.WindowsInstallerLink_DisplayName();
92 }
93
94 public String getDescription() {
95 return Messages.WindowsInstallerLink_Description();
96 }
97
98 /**
99 * Is the installation successful?
100 */
101 public boolean isInstalled() {
102 return installationDir!=null;
103 }
104
105 /**
106 * Performs installation.
107 */
108 public void doDoInstall(StaplerRequest req, StaplerResponse rsp, @QueryParameter("dir") String _dir) throws IOException, ServletException {
109 if(installationDir!=null) {
110 // installation already complete
111 sendError("Installation is already complete",req,rsp);
112 return;
113 }
114 if(!DotNet.isInstalled(2,0)) {
115 sendError(".NET Framework 2.0 or later is required for this feature",req,rsp);
116 return;
117 }
118
119 Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
120
121 File dir = new File(_dir).getAbsoluteFile();
122 dir.mkdirs();
123 if(!dir.exists()) {
124 sendError("Failed to create installation directory: "+dir,req,rsp);
125 return;
126 }
127
128 try {
129 // copy files over there
130 copy(req, rsp, dir, getClass().getResource("/windows-service/jenkins.exe"), "jenkins.exe");
131 copy(req, rsp, dir, getClass().getResource("/windows-service/jenkins.xml"), "jenkins.xml");
132 if(!hudsonWar.getCanonicalFile().equals(new File(dir,"jenkins.war").getCanonicalFile()))
133 copy(req, rsp, dir, hudsonWar.toURI().toURL(), "jenkins.war");
134
135 // install as a service
136 ByteArrayOutputStream baos = new ByteArrayOutputStream();
137 StreamTaskListener task = new StreamTaskListener(baos);
138 task.getLogger().println("Installing a service");
139 int r = runElevated(new File(dir, "jenkins.exe"), "install", task, dir);
140 if(r!=0) {
141 sendError(baos.toString(),req,rsp);
142 return;
143 }
144
145 // installation was successful
146 installationDir = dir;
147 rsp.sendRedirect(".");
148 } catch (AbortException e) {
149 // this exception is used as a signal to terminate processing. the error should have been already reported
150 } catch (InterruptedException e) {
151 throw new ServletException(e);
152 }
153 }
154
155 /**
156 * Copies a single resource into the target folder, by the given name, and handle errors gracefully.
157 */
158 private void copy(StaplerRequest req, StaplerResponse rsp, File dir, URL src, String name) throws ServletException, IOException {
159 try {
160 FileUtils.copyURLToFile(src,new File(dir, name));
161 } catch (IOException e) {
162 LOGGER.log(Level.SEVERE, "Failed to copy "+name,e);
163 sendError("Failed to copy "+name+": "+e.getMessage(),req,rsp);
164 throw new AbortException();
165 }
166 }
167
168 public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
169 if(installationDir==null) {
170 // if the user reloads the page after Hudson has restarted,
171 // it comes back here. In such a case, don't let this restart Hudson.
172 // so just send them back to the top page
173 rsp.sendRedirect(req.getContextPath()+"/");
174 return;
175 }
176 Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
177
178 rsp.forward(this,"_restart",req);
179 final File oldRoot = Jenkins.getInstance().getRootDir();
180
181 // initiate an orderly shutdown after we finished serving this request
182 new Thread("terminator") {
183 public void run() {
184 try {
185 Thread.sleep(1000);
186
187 // let the service start after we close our sockets, to avoid conflicts
188 Runtime.getRuntime().addShutdownHook(new Thread("service starter") {
189 public void run() {
190 try {
191 if(!oldRoot.equals(installationDir)) {
192 LOGGER.info("Moving data");
193 Move mv = new Move();
194 Project p = new Project();
195 p.addBuildListener(createLogger());
196 mv.setProject(p);
197 FileSet fs = new FileSet();
198 fs.setDir(oldRoot);
199 fs.setExcludes("war/**"); // we can't really move the exploded war.
200 mv.addFileset(fs);
201 mv.setTodir(installationDir);
202 mv.setFailOnError(false); // plugins can also fail to move
203 mv.execute();
204 }
205 LOGGER.info("Starting a Windows service");
206 StreamTaskListener task = StreamTaskListener.fromStdout();
207 int r = runElevated(
208 new File(installationDir, "jenkins.exe"), "start", task, installationDir);
209 task.getLogger().println(r==0?"Successfully started":"start service failed. Exit code="+r);
210 } catch (IOException e) {
211 e.printStackTrace();
212 } catch (InterruptedException e) {
213 e.printStackTrace();
214 }
215 }
216
217 private DefaultLogger createLogger() {
218 DefaultLogger logger = new DefaultLogger();
219 logger.setOutputPrintStream(System.out);
220 logger.setErrorPrintStream(System.err);
221 return logger;
222 }
223 });
224
225 System.exit(0);
226 } catch (InterruptedException e) {
227 e.printStackTrace();
228 }
229 }
230 }.start();
231 }
232
233 /**
234 * Displays the error in a page.
235 */
236 protected final void sendError(Exception e, StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException {
237 sendError(e.getMessage(),req,rsp);
238 }
239
240 protected final void sendError(String message, StaplerRequest req, StaplerResponse rsp) throws ServletException, IOException {
241 req.setAttribute("message",message);
242 req.setAttribute("pre",true);
243 rsp.forward(Jenkins.getInstance(),"error",req);
244 }
245
246 /**
247 * Decide if {@link WindowsInstallerLink} should show up in UI, and if so, register it.
248 */
249 @Extension
250 public static WindowsInstallerLink registerIfApplicable() {
251 if(!Functions.isWindows())
252 return null; // this is a Windows only feature
253
254 if(Lifecycle.get() instanceof WindowsServiceLifecycle)
255 return null; // already installed as Windows service
256
257 // this system property is set by the launcher when we run "java -jar jenkins.war"
258 // and this is how we know where is jenkins.war.
259 String war = System.getProperty("executable-war");
260 if(war!=null && new File(war).exists()) {
261 WindowsInstallerLink link = new WindowsInstallerLink(new File(war));
262
263 // in certain situations where we know the user is just trying Jenkins (like when Jenkins is launched
264 // from JNLP), also put this link on the navigation bar to increase
265 // visibility
266 if(System.getProperty(WindowsInstallerLink.class.getName()+".prominent")!=null)
267 Jenkins.getInstance().getActions().add(link);
268
269 return link;
270 }
271
272 return null;
273 }
274
275 /**
276 * Invokes jenkins.exe with a SCM management command.
277 *
278 * <p>
279 * If it fails in a way that indicates the presence of UAC, retry in an UAC compatible manner.
280 */
281 static int runElevated(File jenkinsExe, String command, TaskListener out, File pwd) throws IOException, InterruptedException {
282 try {
283 return new LocalLauncher(out).launch().cmds(jenkinsExe, command).stdout(out).pwd(pwd).join();
284 } catch (IOException e) {
285 if (e.getMessage().contains("CreateProcess") && e.getMessage().contains("=740")) {
286 // fall through
287 } else {
288 throw e;
289 }
290 }
291
292 // error code 740 is ERROR_ELEVATION_REQUIRED, indicating that
293 // we run in UAC-enabled Windows and we need to run this in an elevated privilege
294 SHELLEXECUTEINFO sei = new SHELLEXECUTEINFO();
295 sei.fMask = SEE_MASK_NOCLOSEPROCESS;
296 sei.lpVerb = "runas";
297 sei.lpFile = jenkinsExe.getAbsolutePath();
298 sei.lpParameters = "/redirect redirect.log "+command;
299 sei.lpDirectory = pwd.getAbsolutePath();
300 sei.nShow = SW_HIDE;
301 if (!Shell32.INSTANCE.ShellExecuteEx(sei))
302 throw new IOException("Failed to shellExecute: "+ Native.getLastError());
303
304 try {
305 return Kernel32Utils.waitForExitProcess(sei.hProcess);
306 } finally {
307 FileInputStream fin = new FileInputStream(new File(pwd,"redirect.log"));
308 IOUtils.copy(fin, out.getLogger());
309 fin.close();
310 }
311 }
312
313 private static final Logger LOGGER = Logger.getLogger(WindowsInstallerLink.class.getName());
314 }
315