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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5 * Eric Lefevre-Ardant, Erik Ramfelt, Michael B. Donohue, Alan Harder,
6 * Manufacture Francaise des Pneumatiques Michelin, Romain Seguy
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26 package hudson;
27
28 import hudson.Launcher.LocalLauncher;
29 import hudson.Launcher.RemoteLauncher;
30 import jenkins.model.Jenkins;
31 import hudson.model.TaskListener;
32 import hudson.model.AbstractProject;
33 import hudson.model.Item;
34 import hudson.remoting.Callable;
35 import hudson.remoting.Channel;
36 import hudson.remoting.DelegatingCallable;
37 import hudson.remoting.Future;
38 import hudson.remoting.Pipe;
39 import hudson.remoting.RemoteOutputStream;
40 import hudson.remoting.VirtualChannel;
41 import hudson.remoting.RemoteInputStream;
42 import hudson.remoting.Which;
43 import hudson.security.AccessControlled;
44 import hudson.util.DirScanner;
45 import hudson.util.IOException2;
46 import hudson.util.HeadBufferingStream;
47 import hudson.util.FormValidation;
48 import hudson.util.IOUtils;
49
50 import static hudson.Util.*;
51 import static hudson.util.jna.GNUCLibrary.LIBC;
52 import static hudson.FilePath.TarCompression.GZIP;
53 import hudson.org.apache.tools.tar.TarInputStream;
54 import hudson.util.io.Archiver;
55 import hudson.util.io.ArchiverFactory;
56 import org.apache.tools.ant.BuildException;
57 import org.apache.tools.ant.DirectoryScanner;
58 import org.apache.tools.ant.Project;
59 import org.apache.tools.ant.taskdefs.Copy;
60 import org.apache.tools.ant.types.FileSet;
61 import org.apache.tools.tar.TarEntry;
62 import org.apache.commons.io.input.CountingInputStream;
63 import org.apache.commons.fileupload.FileItem;
64 import org.kohsuke.stapler.Stapler;
65 import java.io.BufferedOutputStream;
66 import java.io.File;
67 import java.io.FileFilter;
68 import java.io.FileInputStream;
69 import java.io.FileOutputStream;
70 import java.io.FileWriter;
71 import java.io.IOException;
72 import java.io.InputStream;
73 import java.io.ObjectInputStream;
74 import java.io.ObjectOutputStream;
75 import java.io.OutputStream;
76 import java.io.Serializable;
77 import java.io.Writer;
78 import java.io.OutputStreamWriter;
79 import java.lang.reflect.Field;
80 import java.net.HttpURLConnection;
81 import java.net.URI;
82 import java.net.URL;
83 import java.net.URLConnection;
84 import java.util.ArrayList;
85 import java.util.List;
86 import java.util.StringTokenizer;
87 import java.util.Arrays;
88 import java.util.Comparator;
89 import java.util.logging.Level;
90 import java.util.regex.Matcher;
91 import java.util.regex.Pattern;
92 import java.util.concurrent.ExecutionException;
93 import java.util.concurrent.TimeUnit;
94 import java.util.concurrent.TimeoutException;
95 import java.util.zip.GZIPOutputStream;
96 import java.util.zip.GZIPInputStream;
97
98 import com.sun.jna.Native;
99 import hudson.os.PosixException;
100 import java.util.Enumeration;
101 import java.util.logging.Logger;
102 import org.apache.tools.ant.taskdefs.Chmod;
103
104 import org.apache.tools.zip.ZipFile;
105 import org.apache.tools.zip.ZipEntry;
106
107 /**
108 * {@link File} like object with remoting support.
109 *
110 * <p>
111 * Unlike {@link File}, which always implies a file path on the current computer,
112 * {@link FilePath} represents a file path on a specific slave or the master.
113 *
114 * Despite that, {@link FilePath} can be used much like {@link File}. It exposes
115 * a bunch of operations (and we should add more operations as long as they are
116 * generally useful), and when invoked against a file on a remote node, {@link FilePath}
117 * executes the necessary code remotely, thereby providing semi-transparent file
118 * operations.
119 *
120 * <h2>Using {@link FilePath} smartly</h2>
121 * <p>
122 * The transparency makes it easy to write plugins without worrying too much about
123 * remoting, by making it works like NFS, where remoting happens at the file-system
124 * layer.
125 *
126 * <p>
127 * But one should note that such use of remoting may not be optional. Sometimes,
128 * it makes more sense to move some computation closer to the data, as opposed to
129 * move the data to the computation. For example, if you are just computing a MD5
130 * digest of a file, then it would make sense to do the digest on the host where
131 * the file is located, as opposed to send the whole data to the master and do MD5
132 * digesting there.
133 *
134 * <p>
135 * {@link FilePath} supports this "code migration" by in the
136 * {@link #act(FileCallable)} method. One can pass in a custom implementation
137 * of {@link FileCallable}, to be executed on the node where the data is located.
138 * The following code shows the example:
139 *
140 * <pre>
141 * FilePath file = ...;
142 *
143 * // make 'file' a fresh empty directory.
144 * file.act(new FileCallable&lt;Void>() {
145 * // if 'file' is on a different node, this FileCallable will
146 * // be transfered to that node and executed there.
147 * public Void invoke(File f,VirtualChannel channel) {
148 * // f and file represents the same thing
149 * f.deleteContents();
150 * f.mkdirs();
151 * }
152 * });
153 * </pre>
154 *
155 * <p>
156 * When {@link FileCallable} is transfered to a remote node, it will be done so
157 * by using the same Java serialization scheme that the remoting module uses.
158 * See {@link Channel} for more about this.
159 *
160 * <p>
161 * {@link FilePath} itself can be sent over to a remote node as a part of {@link Callable}
162 * serialization. For example, sending a {@link FilePath} of a remote node to that
163 * node causes {@link FilePath} to become "local". Similarly, sending a
164 * {@link FilePath} that represents the local computer causes it to become "remote."
165 *
166 * @author Kohsuke Kawaguchi
167 */
168 public final class FilePath implements Serializable {
169 /**
170 * When this {@link FilePath} represents the remote path,
171 * this field is always non-null on master (the field represents
172 * the channel to the remote slave.) When transferred to a slave via remoting,
173 * this field reverts back to null, since it's transient.
174 *
175 * When this {@link FilePath} represents a path on the master,
176 * this field is null on master. When transferred to a slave via remoting,
177 * this field becomes non-null, representing the {@link Channel}
178 * back to the master.
179 *
180 * This is used to determine whether we are running on the master or the slave.
181 */
182 private transient VirtualChannel channel;
183
184 // since the platform of the slave might be different, can't use java.io.File
185 private final String remote;
186
187 /**
188 * Creates a {@link FilePath} that represents a path on the given node.
189 *
190 * @param channel
191 * To create a path that represents a remote path, pass in a {@link Channel}
192 * that's connected to that machine. If null, that means the local file path.
193 */
194 public FilePath(VirtualChannel channel, String remote) {
195 this.channel = channel;
196 this.remote = normalize(remote);
197 }
198
199 /**
200 * To create {@link FilePath} that represents a "local" path.
201 *
202 * <p>
203 * A "local" path means a file path on the computer where the
204 * constructor invocation happened.
205 */
206 public FilePath(File localPath) {
207 this.channel = null;
208 this.remote = normalize(localPath.getPath());
209 }
210
211 /**
212 * Construct a path starting with a base location.
213 * @param base starting point for resolution, and defines channel
214 * @param rel a path which if relative will be resolved against base
215 */
216 public FilePath(FilePath base, String rel) {
217 this.channel = base.channel;
218 this.remote = normalize(resolvePathIfRelative(base, rel));
219 }
220
221 private String resolvePathIfRelative(FilePath base, String rel) {
222 if(isAbsolute(rel)) return rel;
223 if(base.isUnix()) {
224 // shouldn't need this replace, but better safe than sorry
225 return base.remote+'/'+rel.replace('\\','/');
226 } else {
227 // need this replace, see Slave.getWorkspaceFor and AbstractItem.getFullName, nested jobs on Windows
228 // slaves will always have a rel containing at least one '/' character. JENKINS-13649
229 return base.remote+'\\'+rel.replace('/','\\');
230 }
231 }
232
233 /**
234 * Is the given path name an absolute path?
235 */
236 private static boolean isAbsolute(String rel) {
237 return rel.startsWith("/") || DRIVE_PATTERN.matcher(rel).matches();
238 }
239
240 private static final Pattern DRIVE_PATTERN = Pattern.compile("[A-Za-z]:[\\\\/].*"),
241 ABSOLUTE_PREFIX_PATTERN = Pattern.compile("^(\\\\\\\\|(?:[A-Za-z]:)?[\\\\/])[\\\\/]*");
242
243 /**
244 * {@link File#getParent()} etc cannot handle ".." and "." in the path component very well,
245 * so remove them.
246 */
247 private static String normalize(String path) {
248 StringBuilder buf = new StringBuilder();
249 // Check for prefix designating absolute path
250 Matcher m = ABSOLUTE_PREFIX_PATTERN.matcher(path);
251 if (m.find()) {
252 buf.append(m.group(1));
253 path = path.substring(m.end());
254 }
255 boolean isAbsolute = buf.length() > 0;
256 // Split remaining path into tokens, trimming any duplicate or trailing separators
257 List<String> tokens = new ArrayList<String>();
258 int s = 0, end = path.length();
259 for (int i = 0; i < end; i++) {
260 char c = path.charAt(i);
261 if (c == '/' || c == '\\') {
262 tokens.add(path.substring(s, i));
263 s = i;
264 // Skip any extra separator chars
265 while (++i < end && ((c = path.charAt(i)) == '/' || c == '\\')) { }
266 // Add token for separator unless we reached the end
267 if (i < end) tokens.add(path.substring(s, s+1));
268 s = i;
269 }
270 }
271 if (s < end) tokens.add(path.substring(s));
272 // Look through tokens for "." or ".."
273 for (int i = 0; i < tokens.size();) {
274 String token = tokens.get(i);
275 if (token.equals(".")) {
276 tokens.remove(i);
277 if (tokens.size() > 0)
278 tokens.remove(i > 0 ? i - 1 : i);
279 } else if (token.equals("..")) {
280 if (i == 0) {
281 // If absolute path, just remove: /../something
282 // If relative path, not collapsible so leave as-is
283 tokens.remove(0);
284 if (tokens.size() > 0) token += tokens.remove(0);
285 if (!isAbsolute) buf.append(token);
286 } else {
287 // Normalize: remove something/.. plus separator before/after
288 i -= 2;
289 for (int j = 0; j < 3; j++) tokens.remove(i);
290 if (i > 0) tokens.remove(i-1);
291 else if (tokens.size() > 0) tokens.remove(0);
292 }
293 } else
294 i += 2;
295 }
296 // Recombine tokens
297 for (String token : tokens) buf.append(token);
298 if (buf.length() == 0) buf.append('.');
299 return buf.toString();
300 }
301
302 /**
303 * Checks if the remote path is Unix.
304 */
305 boolean isUnix() {
306 // if the path represents a local path, there' no need to guess.
307 if(!isRemote())
308 return File.pathSeparatorChar!=';';
309
310 // note that we can't use the usual File.pathSeparator and etc., as the OS of
311 // the machine where this code runs and the OS that this FilePath refers to may be different.
312
313 // Windows absolute path is 'X:\...', so this is usually a good indication of Windows path
314 if(remote.length()>3 && remote.charAt(1)==':' && remote.charAt(2)=='\\')
315 return false;
316 // Windows can handle '/' as a path separator but Unix can't,
317 // so err on Unix side
318 return remote.indexOf("\\")==-1;
319 }
320
321 /**
322 * Gets the full path of the file on the remote machine.
323 *
324 */
325 public String getRemote() {
326 return remote;
327 }
328
329 /**
330 * Creates a zip file from this directory or a file and sends that to the given output stream.
331 *
332 * @deprecated as of 1.315. Use {@link #zip(OutputStream)} that has more consistent name.
333 */
334 public void createZipArchive(OutputStream os) throws IOException, InterruptedException {
335 zip(os);
336 }
337
338 /**
339 * Creates a zip file from this directory or a file and sends that to the given output stream.
340 */
341 public void zip(OutputStream os) throws IOException, InterruptedException {
342 zip(os,(FileFilter)null);
343 }
344
345 public void zip(FilePath dst) throws IOException, InterruptedException {
346 OutputStream os = dst.write();
347 try {
348 zip(os);
349 } finally {
350 os.close();
351 }
352 }
353
354 /**
355 * Creates a zip file from this directory by using the specified filter,
356 * and sends the result to the given output stream.
357 *
358 * @param filter
359 * Must be serializable since it may be executed remotely. Can be null to add all files.
360 *
361 * @since 1.315
362 */
363 public void zip(OutputStream os, FileFilter filter) throws IOException, InterruptedException {
364 archive(ArchiverFactory.ZIP,os,filter);
365 }
366
367 /**
368 * Creates a zip file from this directory by only including the files that match the given glob.
369 *
370 * @param glob
371 * Ant style glob, like "**&#x2F;*.xml". If empty or null, this method
372 * works like {@link #createZipArchive(OutputStream)}
373 *
374 * @since 1.129
375 * @deprecated as of 1.315
376 * Use {@link #zip(OutputStream,String)} that has more consistent name.
377 */
378 public void createZipArchive(OutputStream os, final String glob) throws IOException, InterruptedException {
379 archive(ArchiverFactory.ZIP,os,glob);
380 }
381
382 /**
383 * Creates a zip file from this directory by only including the files that match the given glob.
384 *
385 * @param glob
386 * Ant style glob, like "**&#x2F;*.xml". If empty or null, this method
387 * works like {@link #createZipArchive(OutputStream)}
388 *
389 * @since 1.315
390 */
391 public void zip(OutputStream os, final String glob) throws IOException, InterruptedException {
392 archive(ArchiverFactory.ZIP,os,glob);
393 }
394
395 /**
396 * Uses the given scanner on 'this' directory to list up files and then archive it to a zip stream.
397 */
398 public int zip(OutputStream out, DirScanner scanner) throws IOException, InterruptedException {
399 return archive(ArchiverFactory.ZIP, out, scanner);
400 }
401
402 /**
403 * Archives this directory into the specified archive format, to the given {@link OutputStream}, by using
404 * {@link DirScanner} to choose what files to include.
405 *
406 * @return
407 * number of files/directories archived. This is only really useful to check for a situation where nothing
408 * is archived.
409 */
410 public int archive(final ArchiverFactory factory, OutputStream os, final DirScanner scanner) throws IOException, InterruptedException {
411 final OutputStream out = (channel!=null)?new RemoteOutputStream(os):os;
412 return act(new FileCallable<Integer>() {
413 public Integer invoke(File f, VirtualChannel channel) throws IOException {
414 Archiver a = factory.create(out);
415 try {
416 scanner.scan(f,a);
417 } finally {
418 a.close();
419 }
420 return a.countEntries();
421 }
422
423 private static final long serialVersionUID = 1L;
424 });
425 }
426
427 public int archive(final ArchiverFactory factory, OutputStream os, final FileFilter filter) throws IOException, InterruptedException {
428 return archive(factory,os,new DirScanner.Filter(filter));
429 }
430
431 public int archive(final ArchiverFactory factory, OutputStream os, final String glob) throws IOException, InterruptedException {
432 return archive(factory,os,new DirScanner.Glob(glob,null));
433 }
434
435 /**
436 * When this {@link FilePath} represents a zip file, extracts that zip file.
437 *
438 * @param target
439 * Target directory to expand files to. All the necessary directories will be created.
440 * @since 1.248
441 * @see #unzipFrom(InputStream)
442 */
443 public void unzip(final FilePath target) throws IOException, InterruptedException {
444 target.act(new FileCallable<Void>() {
445
446 public Void invoke(File dir, VirtualChannel channel) throws IOException {
447 if (FilePath.this.isRemote())
448 unzip(dir, FilePath.this.read()); // use streams
449 else
450 unzip(dir, new File(FilePath.this.getRemote())); // shortcut to local file
451 return null;
452 }
453 private static final long serialVersionUID = 1L;
454 });
455 }
456
457 /**
458 * When this {@link FilePath} represents a tar file, extracts that tar file.
459 *
460 * @param target
461 * Target directory to expand files to. All the necessary directories will be created.
462 * @param compression
463 * Compression mode of this tar file.
464 * @since 1.292
465 * @see #untarFrom(InputStream, TarCompression)
466 */
467 public void untar(final FilePath target, final TarCompression compression) throws IOException, InterruptedException {
468 target.act(new FileCallable<Void>() {
469 public Void invoke(File dir, VirtualChannel channel) throws IOException {
470 readFromTar(FilePath.this.getName(),dir,compression.extract(FilePath.this.read()));
471 return null;
472 }
473 private static final long serialVersionUID = 1L;
474 });
475 }
476
477 /**
478 * Reads the given InputStream as a zip file and extracts it into this directory.
479 *
480 * @param _in
481 * The stream will be closed by this method after it's fully read.
482 * @since 1.283
483 * @see #unzip(FilePath)
484 */
485 public void unzipFrom(InputStream _in) throws IOException, InterruptedException {
486 final InputStream in = new RemoteInputStream(_in);
487 act(new FileCallable<Void>() {
488 public Void invoke(File dir, VirtualChannel channel) throws IOException {
489 unzip(dir, in);
490 return null;
491 }
492 private static final long serialVersionUID = 1L;
493 });
494 }
495
496 private void unzip(File dir, InputStream in) throws IOException {
497 File tmpFile = File.createTempFile("tmpzip", null); // uses java.io.tmpdir
498 try {
499 IOUtils.copy(in, tmpFile);
500 unzip(dir,tmpFile);
501 }
502 finally {
503 tmpFile.delete();
504 }
505 }
506
507 private void unzip(File dir, File zipFile) throws IOException {
508 dir = dir.getAbsoluteFile(); // without absolutization, getParentFile below seems to fail
509 ZipFile zip = new ZipFile(zipFile);
510 @SuppressWarnings("unchecked")
511 Enumeration<ZipEntry> entries = zip.getEntries();
512
513 try {
514 while (entries.hasMoreElements()) {
515 ZipEntry e = entries.nextElement();
516 File f = new File(dir, e.getName());
517 if (e.isDirectory()) {
518 f.mkdirs();
519 } else {
520 File p = f.getParentFile();
521 if (p != null) {
522 p.mkdirs();
523 }
524 IOUtils.copy(zip.getInputStream(e), f);
525 try {
526 FilePath target = new FilePath(f);
527 int mode = e.getUnixMode();
528 if (mode!=0) // Ant returns 0 if the archive doesn't record the access mode
529 target.chmod(mode);
530 } catch (InterruptedException ex) {
531 LOGGER.log(Level.WARNING, "unable to set permissions", ex);
532 }
533 f.setLastModified(e.getTime());
534 }
535 }
536 } finally {
537 zip.close();
538 }
539 }
540
541 /**
542 * Absolutizes this {@link FilePath} and returns the new one.
543 */
544 public FilePath absolutize() throws IOException, InterruptedException {
545 return new FilePath(channel,act(new FileCallable<String>() {
546 private static final long serialVersionUID = 1L;
547 public String invoke(File f, VirtualChannel channel) throws IOException {
548 return f.getAbsolutePath();
549 }
550 }));
551 }
552
553 /**
554 * Creates a symlink to the specified target.
555 *
556 * @param target
557 * The file that the symlink should point to.
558 * @param listener
559 * If symlink creation requires a help of an external process, the error will be reported here.
560 * @since 1.456
561 */
562 public void symlinkTo(final String target, final TaskListener listener) throws IOException, InterruptedException {
563 act(new FileCallable<Void>() {
564 private static final long serialVersionUID = 1L;
565 public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
566 Util.createSymlink(f.getParentFile(),target,f.getName(),listener);
567 return null;
568 }
569 });
570 }
571
572 /**
573 * Resolves symlink, if the given file is a symlink. Otherwise return null.
574 * <p>
575 * If the resolution fails, report an error.
576 *
577 * @since 1.456
578 */
579 public String readLink() throws IOException, InterruptedException {
580 return act(new FileCallable<String>() {
581 private static final long serialVersionUID = 1L;
582 public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
583 return Util.resolveSymlink(f);
584 }
585 });
586 }
587
588 @Override
589 public boolean equals(Object o) {
590 if (this == o) return true;
591 if (o == null || getClass() != o.getClass()) return false;
592
593 FilePath that = (FilePath) o;
594
595 if (channel != null ? !channel.equals(that.channel) : that.channel != null) return false;
596 return remote.equals(that.remote);
597
598 }
599
600 @Override
601 public int hashCode() {
602 return 31 * (channel != null ? channel.hashCode() : 0) + remote.hashCode();
603 }
604
605 /**
606 * Supported tar file compression methods.
607 */
608 public enum TarCompression {
609 NONE {
610 public InputStream extract(InputStream in) {
611 return in;
612 }
613 public OutputStream compress(OutputStream out) {
614 return out;
615 }
616 },
617 GZIP {
618 public InputStream extract(InputStream _in) throws IOException {
619 HeadBufferingStream in = new HeadBufferingStream(_in,SIDE_BUFFER_SIZE);
620 try {
621 return new GZIPInputStream(in,8192);
622 } catch (IOException e) {
623 // various people reported "java.io.IOException: Not in GZIP format" here, so diagnose this problem better
624 in.fillSide();
625 throw new IOException2(e.getMessage()+"\nstream="+Util.toHexString(in.getSideBuffer()),e);
626 }
627 }
628 public OutputStream compress(OutputStream out) throws IOException {
629 return new GZIPOutputStream(new BufferedOutputStream(out));
630 }
631 };
632
633 public abstract InputStream extract(InputStream in) throws IOException;
634 public abstract OutputStream compress(OutputStream in) throws IOException;
635 }
636
637 /**
638 * Reads the given InputStream as a tar file and extracts it into this directory.
639 *
640 * @param _in
641 * The stream will be closed by this method after it's fully read.
642 * @param compression
643 * The compression method in use.
644 * @since 1.292
645 */
646 public void untarFrom(InputStream _in, final TarCompression compression) throws IOException, InterruptedException {
647 try {
648 final InputStream in = new RemoteInputStream(_in);
649 act(new FileCallable<Void>() {
650 public Void invoke(File dir, VirtualChannel channel) throws IOException {
651 readFromTar("input stream",dir, compression.extract(in));
652 return null;
653 }
654 private static final long serialVersionUID = 1L;
655 });
656 } finally {
657 IOUtils.closeQuietly(_in);
658 }
659 }
660
661 /**
662 * Given a tgz/zip file, extracts it to the given target directory, if necessary.
663 *
664 * <p>
665 * This method is a convenience method designed for installing a binary package to a location
666 * that supports upgrade and downgrade. Specifically,
667 *
668 * <ul>
669 * <li>If the target directory doesn't exist {@linkplain #mkdirs() it'll be created}.
670 * <li>The timestamp of the .tgz file is left in the installation directory upon extraction.
671 * <li>If the timestamp left in the directory doesn't match with the timestamp of the current archive file,
672 * the directory contents will be discarded and the archive file will be re-extracted.
673 * <li>If the connection is refused but the target directory already exists, it is left alone.
674 * </ul>
675 *
676 * @param archive
677 * The resource that represents the tgz/zip file. This URL must support the "Last-Modified" header.
678 * (Most common usage is to get this from {@link ClassLoader#getResource(String)})
679 * @param listener
680 * If non-null, a message will be printed to this listener once this method decides to
681 * extract an archive.
682 * @return
683 * true if the archive was extracted. false if the extraction was skipped because the target directory
684 * was considered up to date.
685 * @since 1.299
686 */
687 public boolean installIfNecessaryFrom(URL archive, TaskListener listener, String message) throws IOException, InterruptedException {
688 try {
689 FilePath timestamp = this.child(".timestamp");
690 URLConnection con;
691 try {
692 con = ProxyConfiguration.open(archive);
693 if (timestamp.exists()) {
694 con.setIfModifiedSince(timestamp.lastModified());
695 }
696 con.connect();
697 } catch (IOException x) {
698 if (this.exists()) {
699 // Cannot connect now, so assume whatever was last unpacked is still OK.
700 if (listener != null) {
701 listener.getLogger().println("Skipping installation of " + archive + " to " + remote + ": " + x);
702 }
703 return false;
704 } else {
705 throw x;
706 }
707 }
708
709 if (con instanceof HttpURLConnection
710 && ((HttpURLConnection)con).getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
711 return false;
712 }
713
714 long sourceTimestamp = con.getLastModified();
715
716 if(this.exists()) {
717 if(timestamp.exists() && sourceTimestamp ==timestamp.lastModified())
718 return false; // already up to date
719 this.deleteContents();
720 } else {
721 this.mkdirs();
722 }
723
724 if(listener!=null)
725 listener.getLogger().println(message);
726
727 // for HTTP downloads, enable automatic retry for added resilience
728 InputStream in = archive.getProtocol().startsWith("http") ? ProxyConfiguration.getInputStream(archive) : con.getInputStream();
729 CountingInputStream cis = new CountingInputStream(in);
730 try {
731 if(archive.toExternalForm().endsWith(".zip"))
732 unzipFrom(cis);
733 else
734 untarFrom(cis,GZIP);
735 } catch (IOException e) {
736 throw new IOException2(String.format("Failed to unpack %s (%d bytes read of total %d)",
737 archive,cis.getByteCount(),con.getContentLength()),e);
738 }
739 timestamp.touch(sourceTimestamp);
740 return true;
741 } catch (IOException e) {
742 throw new IOException2("Failed to install "+archive+" to "+remote,e);
743 }
744 }
745
746 /**
747 * Reads the URL on the current VM, and writes all the data to this {@link FilePath}
748 * (this is different from resolving URL remotely.)
749 *
750 * @since 1.293
751 */
752 public void copyFrom(URL url) throws IOException, InterruptedException {
753 InputStream in = url.openStream();
754 try {
755 copyFrom(in);
756 } finally {
757 in.close();
758 }
759 }
760
761 /**
762 * Replaces the content of this file by the data from the given {@link InputStream}.
763 *
764 * @since 1.293
765 */
766 public void copyFrom(InputStream in) throws IOException, InterruptedException {
767 OutputStream os = write();
768 try {
769 IOUtils.copy(in, os);
770 } finally {
771 os.close();
772 }
773 }
774
775 /**
776 * Convenience method to call {@link FilePath#copyTo(FilePath)}.
777 *
778 * @since 1.311
779 */
780 public void copyFrom(FilePath src) throws IOException, InterruptedException {
781 src.copyTo(this);
782 }
783
784 /**
785 * Place the data from {@link FileItem} into the file location specified by this {@link FilePath} object.
786 */
787 public void copyFrom(FileItem file) throws IOException, InterruptedException {
788 if(channel==null) {
789 try {
790 file.write(new File(remote));
791 } catch (IOException e) {
792 throw e;
793 } catch (Exception e) {
794 throw new IOException2(e);
795 }
796 } else {
797 InputStream i = file.getInputStream();
798 OutputStream o = write();
799 try {
800 IOUtils.copy(i,o);
801 } finally {
802 try {
803 o.close();
804 } finally {
805 i.close();
806 }
807 }
808 }
809 }
810
811 /**
812 * Code that gets executed on the machine where the {@link FilePath} is local.
813 * Used to act on {@link FilePath}.
814 *
815 * @see FilePath#act(FileCallable)
816 */
817 public interface FileCallable<T> extends Serializable {
818 /**
819 * Performs the computational task on the node where the data is located.
820 *
821 * <p>
822 * All the exceptions are forwarded to the caller.
823 *
824 * @param f
825 * {@link File} that represents the local file that {@link FilePath} has represented.
826 * @param channel
827 * The "back pointer" of the {@link Channel} that represents the communication
828 * with the node from where the code was sent.
829 */
830 T invoke(File f, VirtualChannel channel) throws IOException, InterruptedException;
831 }
832
833 /**
834 * Executes some program on the machine that this {@link FilePath} exists,
835 * so that one can perform local file operations.
836 */
837 public <T> T act(final FileCallable<T> callable) throws IOException, InterruptedException {
838 return act(callable,callable.getClass().getClassLoader());
839 }
840
841 private <T> T act(final FileCallable<T> callable, ClassLoader cl) throws IOException, InterruptedException {
842 if(channel!=null) {
843 // run this on a remote system
844 try {
845 DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<T>(callable, cl);
846 Jenkins instance = Jenkins.getInstance();
847 if (instance != null) { // this happens during unit tests
848 ExtensionList<FileCallableWrapperFactory> factories = instance.getExtensionList(FileCallableWrapperFactory.class);
849 for (FileCallableWrapperFactory factory : factories) {
850 wrapper = factory.wrap(wrapper);
851 }
852 }
853
854 return channel.call(wrapper);
855 } catch (TunneledInterruptedException e) {
856 throw (InterruptedException)new InterruptedException(e.getMessage()).initCause(e);
857 } catch (AbortException e) {
858 throw e; // pass through so that the caller can catch it as AbortException
859 } catch (IOException e) {
860 // wrap it into a new IOException so that we get the caller's stack trace as well.
861 throw new IOException2("remote file operation failed: "+remote+" at "+channel,e);
862 }
863 } else {
864 // the file is on the local machine.
865 return callable.invoke(new File(remote), Jenkins.MasterComputer.localChannel);
866 }
867 }
868
869 /**
870 * This extension point allows to contribute a wrapper around a fileCallable so that a plugin can "intercept" a
871 * call.
872 * <p>The {@link #wrap(hudson.remoting.DelegatingCallable)} method itself will be executed on master
873 * (and may collect contextual data if needed) and the returned wrapper will be executed on remote.
874 *
875 * @since 1.482
876 * @see AbstractInterceptorCallableWrapper
877 */
878 public static abstract class FileCallableWrapperFactory implements ExtensionPoint {
879
880 public abstract <T> DelegatingCallable<T,IOException> wrap(DelegatingCallable<T,IOException> callable);
881
882 }
883
884 /**
885 * Abstract {@link DelegatingCallable} that exposes an Before/After pattern for
886 * {@link hudson.FilePath.FileCallableWrapperFactory} that want to implement AOP-style interceptors
887 * @since 1.482
888 */
889 public static abstract class AbstractInterceptorCallableWrapper<T> implements DelegatingCallable<T, IOException> {
890 private static final long serialVersionUID = 1L;
891
892 private final DelegatingCallable<T, IOException> callable;
893
894 public AbstractInterceptorCallableWrapper(DelegatingCallable<T, IOException> callable) {
895 this.callable = callable;
896 }
897
898 @Override
899 public final ClassLoader getClassLoader() {
900 return callable.getClassLoader();
901 }
902
903 public final T call() throws IOException {
904 before();
905 try {
906 return callable.call();
907 } finally {
908 after();
909 }
910 }
911
912 /**
913 * Executed before the actual FileCallable is invoked. This code will run on remote
914 */
915 protected void before() {}
916
917 /**
918 * Executed after the actual FileCallable is invoked (even if this one failed). This code will run on remote
919 */
920 protected void after() {}
921 }
922
923
924 /**
925 * Executes some program on the machine that this {@link FilePath} exists,
926 * so that one can perform local file operations.
927 */
928 public <T> Future<T> actAsync(final FileCallable<T> callable) throws IOException, InterruptedException {
929 try {
930 DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<T>(callable);
931 Jenkins instance = Jenkins.getInstance();
932 if (instance != null) { // this happens during unit tests
933 ExtensionList<FileCallableWrapperFactory> factories = instance.getExtensionList(FileCallableWrapperFactory.class);
934 for (FileCallableWrapperFactory factory : factories) {
935 wrapper = factory.wrap(wrapper);
936 }
937 }
938
939 return (channel!=null ? channel : Jenkins.MasterComputer.localChannel)
940 .callAsync(wrapper);
941 } catch (IOException e) {
942 // wrap it into a new IOException so that we get the caller's stack trace as well.
943 throw new IOException2("remote file operation failed",e);
944 }
945 }
946
947 /**
948 * Executes some program on the machine that this {@link FilePath} exists,
949 * so that one can perform local file operations.
950 */
951 public <V,E extends Throwable> V act(Callable<V,E> callable) throws IOException, InterruptedException, E {
952 if(channel!=null) {
953 // run this on a remote system
954 return channel.call(callable);
955 } else {
956 // the file is on the local machine
957 return callable.call();
958 }
959 }
960
961 /**
962 * Converts this file to the URI, relative to the machine
963 * on which this file is available.
964 */
965 public URI toURI() throws IOException, InterruptedException {
966 return act(new FileCallable<URI>() {
967 private static final long serialVersionUID = 1L;
968 public URI invoke(File f, VirtualChannel channel) {
969 return f.toURI();
970 }
971 });
972 }
973
974 /**
975 * Creates this directory.
976 */
977 public void mkdirs() throws IOException, InterruptedException {
978 if(!act(new FileCallable<Boolean>() {
979 private static final long serialVersionUID = 1L;
980 public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
981 if(f.mkdirs() || f.exists())
982 return true; // OK
983
984 // following Ant <mkdir> task to avoid possible race condition.
985 Thread.sleep(10);
986
987 return f.mkdirs() || f.exists();
988 }
989 }))
990 throw new IOException("Failed to mkdirs: "+remote);
991 }
992
993 /**
994 * Deletes this directory, including all its contents recursively.
995 */
996 public void deleteRecursive() throws IOException, InterruptedException {
997 act(new FileCallable<Void>() {
998 private static final long serialVersionUID = 1L;
999 public Void invoke(File f, VirtualChannel channel) throws IOException {
1000 Util.deleteRecursive(f);
1001 return null;
1002 }
1003 });
1004 }
1005
1006 /**
1007 * Deletes all the contents of this directory, but not the directory itself
1008 */
1009 public void deleteContents() throws IOException, InterruptedException {
1010 act(new FileCallable<Void>() {
1011 private static final long serialVersionUID = 1L;
1012 public Void invoke(File f, VirtualChannel channel) throws IOException {
1013 Util.deleteContentsRecursive(f);
1014 return null;
1015 }
1016 });
1017 }
1018
1019 /**
1020 * Gets the file name portion except the extension.
1021 *
1022 * For example, "foo" for "foo.txt" and "foo.tar" for "foo.tar.gz".
1023 */
1024 public String getBaseName() {
1025 String n = getName();
1026 int idx = n.lastIndexOf('.');
1027 if (idx<0) return n;
1028 return n.substring(0,idx);
1029 }
1030 /**
1031 * Gets just the file name portion without directories.
1032 *
1033 * For example, "foo.txt" for "../abc/foo.txt"
1034 */
1035 public String getName() {
1036 String r = remote;
1037 if(r.endsWith("\\") || r.endsWith("/"))
1038 r = r.substring(0,r.length()-1);
1039
1040 int len = r.length()-1;
1041 while(len>=0) {
1042 char ch = r.charAt(len);
1043 if(ch=='\\' || ch=='/')
1044 break;
1045 len--;
1046 }
1047
1048 return r.substring(len+1);
1049 }
1050
1051 /**
1052 * Short for {@code getParent().child(rel)}. Useful for getting other files in the same directory.
1053 */
1054 public FilePath sibling(String rel) {
1055 return getParent().child(rel);
1056 }
1057
1058 /**
1059 * Returns a {@link FilePath} by adding the given suffix to this path name.
1060 */
1061 public FilePath withSuffix(String suffix) {
1062 return new FilePath(channel,remote+suffix);
1063 }
1064
1065 /**
1066 * The same as {@link FilePath#FilePath(FilePath,String)} but more OO.
1067 * @param relOrAbsolute a relative or absolute path
1068 * @return a file on the same channel
1069 */
1070 public FilePath child(String relOrAbsolute) {
1071 return new FilePath(this,relOrAbsolute);
1072 }
1073
1074 /**
1075 * Gets the parent file.
1076 * @return parent FilePath or null if there is no parent
1077 */
1078 public FilePath getParent() {
1079 int i = remote.length() - 2;
1080 for (; i >= 0; i--) {
1081 char ch = remote.charAt(i);
1082 if(ch=='\\' || ch=='/')
1083 break;
1084 }
1085
1086 return i >= 0 ? new FilePath( channel, remote.substring(0,i+1) ) : null;
1087 }
1088
1089 /**
1090 * Creates a temporary file in the directory that this {@link FilePath} object designates.
1091 *
1092 * @param prefix
1093 * The prefix string to be used in generating the file's name; must be
1094 * at least three characters long
1095 * @param suffix
1096 * The suffix string to be used in generating the file's name; may be
1097 * null, in which case the suffix ".tmp" will be used
1098 * @return
1099 * The new FilePath pointing to the temporary file
1100 * @see File#createTempFile(String, String)
1101 */
1102 public FilePath createTempFile(final String prefix, final String suffix) throws IOException, InterruptedException {
1103 try {
1104 return new FilePath(this,act(new FileCallable<String>() {
1105 private static final long serialVersionUID = 1L;
1106 public String invoke(File dir, VirtualChannel channel) throws IOException {
1107 File f = File.createTempFile(prefix, suffix, dir);
1108 return f.getName();
1109 }
1110 }));
1111 } catch (IOException e) {
1112 throw new IOException2("Failed to create a temp file on "+remote,e);
1113 }
1114 }
1115
1116 /**
1117 * Creates a temporary file in this directory and set the contents to the
1118 * given text (encoded in the platform default encoding)
1119 *
1120 * @param prefix
1121 * The prefix string to be used in generating the file's name; must be
1122 * at least three characters long
1123 * @param suffix
1124 * The suffix string to be used in generating the file's name; may be
1125 * null, in which case the suffix ".tmp" will be used
1126 * @param contents
1127 * The initial contents of the temporary file.
1128 * @return
1129 * The new FilePath pointing to the temporary file
1130 * @see File#createTempFile(String, String)
1131 */
1132 public FilePath createTextTempFile(final String prefix, final String suffix, final String contents) throws IOException, InterruptedException {
1133 return createTextTempFile(prefix,suffix,contents,true);
1134 }
1135
1136 /**
1137 * Creates a temporary file in this directory (or the system temporary
1138 * directory) and set the contents to the given text (encoded in the
1139 * platform default encoding)
1140 *
1141 * @param prefix
1142 * The prefix string to be used in generating the file's name; must be
1143 * at least three characters long
1144 * @param suffix
1145 * The suffix string to be used in generating the file's name; may be
1146 * null, in which case the suffix ".tmp" will be used
1147 * @param contents
1148 * The initial contents of the temporary file.
1149 * @param inThisDirectory
1150 * If true, then create this temporary in the directory pointed to by
1151 * this.
1152 * If false, then the temporary file is created in the system temporary
1153 * directory (java.io.tmpdir)
1154 * @return
1155 * The new FilePath pointing to the temporary file
1156 * @see File#createTempFile(String, String)
1157 */
1158 public FilePath createTextTempFile(final String prefix, final String suffix, final String contents, final boolean inThisDirectory) throws IOException, InterruptedException {
1159 try {
1160 return new FilePath(channel,act(new FileCallable<String>() {
1161 private static final long serialVersionUID = 1L;
1162 public String invoke(File dir, VirtualChannel channel) throws IOException {
1163 if(!inThisDirectory)
1164 dir = new File(System.getProperty("java.io.tmpdir"));
1165 else
1166 dir.mkdirs();
1167
1168 File f;
1169 try {
1170 f = File.createTempFile(prefix, suffix, dir);
1171 } catch (IOException e) {
1172 throw new IOException2("Failed to create a temporary directory in "+dir,e);
1173 }
1174
1175 Writer w = new FileWriter(f);
1176 w.write(contents);
1177 w.close();
1178
1179 return f.getAbsolutePath();
1180 }
1181 }));
1182 } catch (IOException e) {
1183 throw new IOException2("Failed to create a temp file on "+remote,e);
1184 }
1185 }
1186
1187 /**
1188 * Creates a temporary directory inside the directory represented by 'this'
1189 *
1190 * @param prefix
1191 * The prefix string to be used in generating the directory's name;
1192 * must be at least three characters long
1193 * @param suffix
1194 * The suffix string to be used in generating the directory's name; may
1195 * be null, in which case the suffix ".tmp" will be used
1196 * @return
1197 * The new FilePath pointing to the temporary directory
1198 * @since 1.311
1199 * @see File#createTempFile(String, String)
1200 */
1201 public FilePath createTempDir(final String prefix, final String suffix) throws IOException, InterruptedException {
1202 try {
1203 return new FilePath(this,act(new FileCallable<String>() {
1204 private static final long serialVersionUID = 1L;
1205 public String invoke(File dir, VirtualChannel channel) throws IOException {
1206 File f = File.createTempFile(prefix, suffix, dir);
1207 f.delete();
1208 f.mkdir();
1209 return f.getName();
1210 }
1211 }));
1212 } catch (IOException e) {
1213 throw new IOException2("Failed to create a temp directory on "+remote,e);
1214 }
1215 }
1216
1217 /**
1218 * Deletes this file.
1219 * @throws IOException if it exists but could not be successfully deleted
1220 * @return true, for a modicum of compatibility
1221 */
1222 public boolean delete() throws IOException, InterruptedException {
1223 act(new FileCallable<Void>() {
1224 private static final long serialVersionUID = 1L;
1225 public Void invoke(File f, VirtualChannel channel) throws IOException {
1226 Util.deleteFile(f);
1227 return null;
1228 }
1229 });
1230 return true;
1231 }
1232
1233 /**
1234 * Checks if the file exists.
1235 */
1236 public boolean exists() throws IOException, InterruptedException {
1237 return act(new FileCallable<Boolean>() {
1238 private static final long serialVersionUID = 1L;
1239 public Boolean invoke(File f, VirtualChannel channel) throws IOException {
1240 return f.exists();
1241 }
1242 });
1243 }
1244
1245 /**
1246 * Gets the last modified time stamp of this file, by using the clock
1247 * of the machine where this file actually resides.
1248 *
1249 * @see File#lastModified()
1250 * @see #touch(long)
1251 */
1252 public long lastModified() throws IOException, InterruptedException {
1253 return act(new FileCallable<Long>() {
1254 private static final long serialVersionUID = 1L;
1255 public Long invoke(File f, VirtualChannel channel) throws IOException {
1256 return f.lastModified();
1257 }
1258 });
1259 }
1260
1261 /**
1262 * Creates a file (if not already exist) and sets the timestamp.
1263 *
1264 * @since 1.299
1265 */
1266 public void touch(final long timestamp) throws IOException, InterruptedException {
1267 act(new FileCallable<Void>() {
1268 private static final long serialVersionUID = -5094638816500738429L;
1269 public Void invoke(File f, VirtualChannel channel) throws IOException {
1270 if(!f.exists())
1271 new FileOutputStream(f).close();
1272 if(!f.setLastModified(timestamp))
1273 throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp);
1274 return null;
1275 }
1276 });
1277 }
1278
1279 private void setLastModifiedIfPossible(final long timestamp) throws IOException, InterruptedException {
1280 String message = act(new FileCallable<String>() {
1281 private static final long serialVersionUID = -828220335793641630L;
1282 public String invoke(File f, VirtualChannel channel) throws IOException {
1283 if(!f.setLastModified(timestamp)) {
1284 if (Functions.isWindows()) {
1285 // On Windows this seems to fail often. See JENKINS-11073
1286 // Therefore don't fail, but just log a warning
1287 return "Failed to set the timestamp of "+f+" to "+timestamp;
1288 } else {
1289 throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp);
1290 }
1291 }
1292 return null;
1293 }
1294 });
1295
1296 if (message!=null) {
1297 LOGGER.warning(message);
1298 }
1299 }
1300
1301 /**
1302 * Checks if the file is a directory.
1303 */
1304 public boolean isDirectory() throws IOException, InterruptedException {
1305 return act(new FileCallable<Boolean>() {
1306 private static final long serialVersionUID = 1L;
1307 public Boolean invoke(File f, VirtualChannel channel) throws IOException {
1308 return f.isDirectory();
1309 }
1310 });
1311 }
1312
1313 /**
1314 * Returns the file size in bytes.
1315 *
1316 * @since 1.129
1317 */
1318 public long length() throws IOException, InterruptedException {
1319 return act(new FileCallable<Long>() {
1320 private static final long serialVersionUID = 1L;
1321 public Long invoke(File f, VirtualChannel channel) throws IOException {
1322 return f.length();
1323 }
1324 });
1325 }
1326
1327 /**
1328 * Sets the file permission.
1329 *
1330 * On Windows, no-op.
1331 *
1332 * @param mask
1333 * File permission mask. To simplify the permission copying,
1334 * if the parameter is -1, this method becomes no-op.
1335 * <p>
1336 * please note mask is expected to be an octal if you use <a href="http://en.wikipedia.org/wiki/Chmod">chmod command line values</a>,
1337 * so preceded by a '0' in java notation, ie <code>chmod(0644)</code>
1338 *
1339 * @since 1.303
1340 * @see #mode()
1341 */
1342 public void chmod(final int mask) throws IOException, InterruptedException {
1343 if(!isUnix() || mask==-1) return;
1344 act(new FileCallable<Void>() {
1345 private static final long serialVersionUID = 1L;
1346 public Void invoke(File f, VirtualChannel channel) throws IOException {
1347 _chmod(f, mask);
1348
1349 return null;
1350 }
1351 });
1352 }
1353
1354 /**
1355 * Run chmod via libc if we can, otherwise fall back to Ant.
1356 */
1357 private static void _chmod(File f, int mask) throws IOException {
1358 if (Functions.isWindows()) return; // noop
1359
1360 try {
1361 if(LIBC.chmod(f.getAbsolutePath(),mask)!=0) {
1362 throw new IOException("Failed to chmod "+f+" : "+LIBC.strerror(Native.getLastError()));
1363 }
1364 } catch(NoClassDefFoundError e) { // cf. https://groups.google.com/group/hudson-dev/browse_thread/thread/6d16c3e8ea0dbc9?hl=fr
1365 _chmodAnt(f, mask);
1366 } catch(UnsatisfiedLinkError e2) { // HUDSON-8155: use Ant's chmod task on non-GNU C systems
1367 _chmodAnt(f, mask);
1368 }
1369 }
1370
1371 private static void _chmodAnt(File f, int mask) {
1372 if (!CHMOD_WARNED) { // only warn this once to avoid flooding the log
1373 CHMOD_WARNED = true;
1374 LOGGER.warning("GNU C Library not available: Using Ant's chmod task instead.");
1375 }
1376 Chmod chmodTask = new Chmod();
1377 chmodTask.setProject(new Project());
1378 chmodTask.setFile(f);
1379 chmodTask.setPerm(Integer.toOctalString(mask));
1380 chmodTask.execute();
1381 }
1382
1383 private static boolean CHMOD_WARNED = false;
1384
1385 /**
1386 * Gets the file permission bit mask.
1387 *
1388 * @return
1389 * -1 on Windows, since such a concept doesn't make sense.
1390 * @since 1.311
1391 * @see #chmod(int)
1392 */
1393 public int mode() throws IOException, InterruptedException, PosixException {
1394 if(!isUnix()) return -1;
1395 return act(new FileCallable<Integer>() {
1396 private static final long serialVersionUID = 1L;
1397 public Integer invoke(File f, VirtualChannel channel) throws IOException {
1398 return IOUtils.mode(f);
1399 }
1400 });
1401 }
1402
1403 /**
1404 * List up files and directories in this directory.
1405 *
1406 * <p>
1407 * This method returns direct children of the directory denoted by the 'this' object.
1408 */
1409 public List<FilePath> list() throws IOException, InterruptedException {
1410 return list((FileFilter)null);
1411 }
1412
1413 /**
1414 * List up subdirectories.
1415 *
1416 * @return can be empty but never null. Doesn't contain "." and ".."
1417 */
1418 public List<FilePath> listDirectories() throws IOException, InterruptedException {
1419 return list(new DirectoryFilter());
1420 }
1421
1422 private static final class DirectoryFilter implements FileFilter, Serializable {
1423 public boolean accept(File f) {
1424 return f.isDirectory();
1425 }
1426 private static final long serialVersionUID = 1L;
1427 }
1428
1429 /**
1430 * List up files in this directory, just like {@link File#listFiles(FileFilter)}.
1431 *
1432 * @param filter
1433 * The optional filter used to narrow down the result.
1434 * If non-null, must be {@link Serializable}.
1435 * If this {@link FilePath} represents a remote path,
1436 * the filter object will be executed on the remote machine.
1437 */
1438 public List<FilePath> list(final FileFilter filter) throws IOException, InterruptedException {
1439 if (filter != null && !(filter instanceof Serializable)) {
1440 throw new IllegalArgumentException("Non-serializable filter of " + filter.getClass());
1441 }
1442 return act(new FileCallable<List<FilePath>>() {
1443 private static final long serialVersionUID = 1L;
1444 public List<FilePath> invoke(File f, VirtualChannel channel) throws IOException {
1445 File[] children = f.listFiles(filter);
1446 if(children ==null) return null;
1447
1448 ArrayList<FilePath> r = new ArrayList<FilePath>(children.length);
1449 for (File child : children)
1450 r.add(new FilePath(child));
1451
1452 return r;
1453 }
1454 }, (filter!=null?filter:this).getClass().getClassLoader());
1455 }
1456
1457 /**
1458 * List up files in this directory that matches the given Ant-style filter.
1459 *
1460 * @param includes
1461 * See {@link FileSet} for the syntax. String like "foo/*.zip" or "foo/*&#42;/*.xml"
1462 * @return
1463 * can be empty but always non-null.
1464 */
1465 public FilePath[] list(final String includes) throws IOException, InterruptedException {
1466 return list(includes, null);
1467 }
1468
1469 /**
1470 * List up files in this directory that matches the given Ant-style filter.
1471 *
1472 * @param includes
1473 * @param excludes
1474 * See {@link FileSet} for the syntax. String like "foo/*.zip" or "foo/*&#42;/*.xml"
1475 * @return
1476 * can be empty but always non-null.
1477 * @since 1.407
1478 */
1479 public FilePath[] list(final String includes, final String excludes) throws IOException, InterruptedException {
1480 return list(includes, excludes, true);
1481 }
1482
1483 /**
1484 * List up files in this directory that matches the given Ant-style filter.
1485 *
1486 * @param includes
1487 * @param excludes
1488 * See {@link FileSet} for the syntax. String like "foo/*.zip" or "foo/*&#42;/*.xml"
1489 * @param defaultExcludes whether to use the ant default excludes
1490 * @return
1491 * can be empty but always non-null.
1492 * @since 1.465
1493 */
1494 public FilePath[] list(final String includes, final String excludes, final boolean defaultExcludes) throws IOException, InterruptedException {
1495 return act(new FileCallable<FilePath[]>() {
1496 private static final long serialVersionUID = 1L;
1497 public FilePath[] invoke(File f, VirtualChannel channel) throws IOException {
1498 String[] files = glob(f, includes, excludes, defaultExcludes);
1499
1500 FilePath[] r = new FilePath[files.length];
1501 for( int i=0; i<r.length; i++ )
1502 r[i] = new FilePath(new File(f,files[i]));
1503
1504 return r;
1505 }
1506 });
1507 }
1508
1509 /**
1510 * Runs Ant glob expansion.
1511 *
1512 * @return
1513 * A set of relative file names from the base directory.
1514 */
1515 private static String[] glob(File dir, String includes, String excludes, boolean defaultExcludes) throws IOException {
1516 if(isAbsolute(includes))
1517 throw new IOException("Expecting Ant GLOB pattern, but saw '"+includes+"'. See http://ant.apache.org/manual/Types/fileset.html for syntax");
1518 FileSet fs = Util.createFileSet(dir,includes,excludes);
1519 fs.setDefaultexcludes(defaultExcludes);
1520 DirectoryScanner ds = fs.getDirectoryScanner(new Project());
1521 String[] files = ds.getIncludedFiles();
1522 return files;
1523 }
1524
1525 /**
1526 * Reads this file.
1527 */
1528 public InputStream read() throws IOException {
1529 if(channel==null)
1530 return new FileInputStream(new File(remote));
1531
1532 final Pipe p = Pipe.createRemoteToLocal();
1533 channel.callAsync(new Callable<Void,IOException>() {
1534 private static final long serialVersionUID = 1L;
1535 public Void call() throws IOException {
1536 FileInputStream fis=null;
1537 try {
1538 fis = new FileInputStream(new File(remote));
1539 Util.copyStream(fis,p.getOut());
1540 return null;
1541 } finally {
1542 IOUtils.closeQuietly(fis);
1543 IOUtils.closeQuietly(p.getOut());
1544 }
1545 }
1546 });
1547
1548 return p.getIn();
1549 }
1550
1551 /**
1552 * Reads this file into a string, by using the current system encoding.
1553 */
1554 public String readToString() throws IOException {
1555 InputStream in = read();
1556 try {
1557 return IOUtils.toString(in);
1558 } finally {
1559 in.close();
1560 }
1561 }
1562
1563 /**
1564 * Writes to this file.
1565 * If this file already exists, it will be overwritten.
1566 * If the directory doesn't exist, it will be created.
1567 *
1568 * <P>
1569 * I/O operation to remote {@link FilePath} happens asynchronously, meaning write operations to the returned
1570 * {@link OutputStream} will return without receiving a confirmation from the remote that the write happened.
1571 * I/O operations also happens asynchronously from the {@link Channel#call(Callable)} operations, so if
1572 * you write to a remote file and then execute {@link Channel#call(Callable)} and try to access the newly copied
1573 * file, it might not be fully written yet.
1574 *
1575 * <p>
1576 *
1577 */
1578 public OutputStream write() throws IOException, InterruptedException {
1579 if(channel==null) {
1580 File f = new File(remote).getAbsoluteFile();
1581 f.getParentFile().mkdirs();
1582 return new FileOutputStream(f);
1583 }
1584
1585 return channel.call(new Callable<OutputStream,IOException>() {
1586 private static final long serialVersionUID = 1L;
1587 public OutputStream call() throws IOException {
1588 File f = new File(remote).getAbsoluteFile();
1589 f.getParentFile().mkdirs();
1590 FileOutputStream fos = new FileOutputStream(f);
1591 return new RemoteOutputStream(fos);
1592 }
1593 });
1594 }
1595
1596 /**
1597 * Overwrites this file by placing the given String as the content.
1598 *
1599 * @param encoding
1600 * Null to use the platform default encoding.
1601 * @since 1.105
1602 */
1603 public void write(final String content, final String encoding) throws IOException, InterruptedException {
1604 act(new FileCallable<Void>() {
1605 private static final long serialVersionUID = 1L;
1606 public Void invoke(File f, VirtualChannel channel) throws IOException {
1607 f.getParentFile().mkdirs();
1608 FileOutputStream fos = new FileOutputStream(f);
1609 Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos);
1610 try {
1611 w.write(content);
1612 } finally {
1613 w.close();
1614 }
1615 return null;
1616 }
1617 });
1618 }
1619
1620 /**
1621 * Computes the MD5 digest of the file in hex string.
1622 */
1623 public String digest() throws IOException, InterruptedException {
1624 return act(new FileCallable<String>() {
1625 private static final long serialVersionUID = 1L;
1626 public String invoke(File f, VirtualChannel channel) throws IOException {
1627 return Util.getDigestOf(new FileInputStream(f));
1628 }
1629 });
1630 }
1631
1632 /**
1633 * Rename this file/directory to the target filepath. This FilePath and the target must
1634 * be on the some host
1635 */
1636 public void renameTo(final FilePath target) throws IOException, InterruptedException {
1637 if(this.channel != target.channel) {
1638 throw new IOException("renameTo target must be on the same host");
1639 }
1640 act(new FileCallable<Void>() {
1641 private static final long serialVersionUID = 1L;
1642 public Void invoke(File f, VirtualChannel channel) throws IOException {
1643 f.renameTo(new File(target.remote));
1644 return null;
1645 }
1646 });
1647 }
1648
1649 /**
1650 * Moves all the contents of this directory into the specified directory, then delete this directory itself.
1651 *
1652 * @since 1.308.
1653 */
1654 public void moveAllChildrenTo(final FilePath target) throws IOException, InterruptedException {
1655 if(this.channel != target.channel) {
1656 throw new IOException("pullUpTo target must be on the same host");
1657 }
1658 act(new FileCallable<Void>() {
1659 private static final long serialVersionUID = 1L;
1660 public Void invoke(File f, VirtualChannel channel) throws IOException {
1661 File t = new File(target.getRemote());
1662
1663 for(File child : f.listFiles()) {
1664 File target = new File(t, child.getName());
1665 if(!child.renameTo(target))
1666 throw new IOException("Failed to rename "+child+" to "+target);
1667 }
1668 f.delete();
1669 return null;
1670 }
1671 });
1672 }
1673
1674 /**
1675 * Copies this file to the specified target.
1676 */
1677 public void copyTo(FilePath target) throws IOException, InterruptedException {
1678 try {
1679 OutputStream out = target.write();
1680 try {
1681 copyTo(out);
1682 } finally {
1683 out.close();
1684 }
1685 } catch (IOException e) {
1686 throw new IOException2("Failed to copy "+this+" to "+target,e);
1687 }
1688 }
1689
1690 /**
1691 * Copies this file to the specified target, with file permissions and other meta attributes intact.
1692 * @since 1.311
1693 */
1694 public void copyToWithPermission(FilePath target) throws IOException, InterruptedException {
1695 copyTo(target);
1696 // copy file permission
1697 target.chmod(mode());
1698 target.setLastModifiedIfPossible(lastModified());
1699 }
1700
1701 /**
1702 * Sends the contents of this file into the given {@link OutputStream}.
1703 */
1704 public void copyTo(OutputStream os) throws IOException, InterruptedException {
1705 final OutputStream out = new RemoteOutputStream(os);
1706
1707 act(new FileCallable<Void>() {
1708 private static final long serialVersionUID = 4088559042349254141L;
1709 public Void invoke(File f, VirtualChannel channel) throws IOException {
1710 FileInputStream fis = null;
1711 try {
1712 fis = new FileInputStream(f);
1713 Util.copyStream(fis,out);
1714 return null;
1715 } finally {
1716 IOUtils.closeQuietly(fis);
1717 IOUtils.closeQuietly(out);
1718 }
1719 }
1720 });
1721
1722 // make sure the writes fully got delivered to 'os' before we return.
1723 // this is needed because I/O operation is asynchronous
1724 syncIO();
1725 }
1726
1727 /**
1728 * With fix to JENKINS-11251 (remoting 2.15), this is no longer necessary.
1729 * But I'm keeping it for a while so that users who manually deploy slave.jar has time to deploy new version
1730 * before this goes away.
1731 */
1732 private void syncIO() throws InterruptedException {
1733 try {
1734 if (channel!=null)
1735 _syncIO();
1736 } catch (AbstractMethodError e) {
1737 // legacy slave.jar. Handle this gracefully
1738 try {
1739 LOGGER.log(Level.WARNING,"Looks like an old slave.jar. Please update "+ Which.jarFile(Channel.class)+" to the new version",e);
1740 } catch (IOException _) {
1741 // really ignore this time
1742 }
1743 }
1744 }
1745
1746 /**
1747 * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
1748 * on BugParade for more details.
1749 */
1750 private void _syncIO() throws InterruptedException {
1751 channel.syncLocalIO();
1752 }
1753
1754 /**
1755 * Remoting interface used for {@link FilePath#copyRecursiveTo(String, FilePath)}.
1756 *
1757 * TODO: this might not be the most efficient way to do the copy.
1758 */
1759 interface RemoteCopier {
1760 /**
1761 * @param fileName
1762 * relative path name to the output file. Path separator must be '/'.
1763 */
1764 void open(String fileName) throws IOException;
1765 void write(byte[] buf, int len) throws IOException;
1766 void close() throws IOException;
1767 }
1768
1769 /**
1770 * Copies the contents of this directory recursively into the specified target directory.
1771 *
1772 * @return
1773 * the number of files copied.
1774 * @since 1.312
1775 */
1776 public int copyRecursiveTo(FilePath target) throws IOException, InterruptedException {
1777 return copyRecursiveTo("**/*",target);
1778 }
1779
1780 /**
1781 * Copies the files that match the given file mask to the specified target node.
1782 *
1783 * @param fileMask
1784 * Ant GLOB pattern.
1785 * String like "foo/bar/*.xml" Multiple patterns can be separated
1786 * by ',', and whitespace can surround ',' (so that you can write
1787 * "abc, def" and "abc,def" to mean the same thing.
1788 * @return
1789 * the number of files copied.
1790 */
1791 public int copyRecursiveTo(String fileMask, FilePath target) throws IOException, InterruptedException {
1792 return copyRecursiveTo(fileMask,null,target);
1793 }
1794
1795 /**
1796 * Copies the files that match the given file mask to the specified target node.
1797 *
1798 * @param fileMask
1799 * Ant GLOB pattern.
1800 * String like "foo/bar/*.xml" Multiple patterns can be separated
1801 * by ',', and whitespace can surround ',' (so that you can write
1802 * "abc, def" and "abc,def" to mean the same thing.
1803 * @param excludes
1804 * Files to be excluded. Can be null.
1805 * @return
1806 * the number of files copied.
1807 */
1808 public int copyRecursiveTo(final String fileMask, final String excludes, final FilePath target) throws IOException, InterruptedException {
1809 if(this.channel==target.channel) {
1810 // local to local copy.
1811 return act(new FileCallable<Integer>() {
1812 private static final long serialVersionUID = 1L;
1813 public Integer invoke(File base, VirtualChannel channel) throws IOException {
1814 if(!base.exists()) return 0;
1815 assert target.channel==null;
1816
1817 try {
1818 class CopyImpl extends Copy {
1819 private int copySize;
1820
1821 public CopyImpl() {
1822 setProject(new org.apache.tools.ant.Project());
1823 }
1824
1825 @Override
1826 protected void doFileOperations() {
1827 copySize = super.fileCopyMap.size();
1828 super.doFileOperations();
1829 }
1830
1831 public int getNumCopied() {
1832 return copySize;
1833 }
1834 }
1835
1836 CopyImpl copyTask = new CopyImpl();
1837 copyTask.setTodir(new File(target.remote));
1838 copyTask.addFileset(Util.createFileSet(base,fileMask,excludes));
1839 copyTask.setOverwrite(true);
1840 copyTask.setIncludeEmptyDirs(false);
1841
1842 copyTask.execute();
1843 return copyTask.getNumCopied();
1844 } catch (BuildException e) {
1845 throw new IOException2("Failed to copy "+base+"/"+fileMask+" to "+target,e);
1846 }
1847 }
1848 });
1849 } else
1850 if(this.channel==null) {
1851 // local -> remote copy
1852 final Pipe pipe = Pipe.createLocalToRemote();
1853
1854 Future<Void> future = target.actAsync(new FileCallable<Void>() {
1855 private static final long serialVersionUID = 1L;
1856 public Void invoke(File f, VirtualChannel channel) throws IOException {
1857 try {
1858 readFromTar(remote+'/'+fileMask, f,TarCompression.GZIP.extract(pipe.getIn()));
1859 return null;
1860 } finally {
1861 pipe.getIn().close();
1862 }
1863 }
1864 });
1865 int r = writeToTar(new File(remote),fileMask,excludes,TarCompression.GZIP.compress(pipe.getOut()));
1866 try {
1867 future.get();
1868 } catch (ExecutionException e) {
1869 throw new IOException2(e);
1870 }
1871 return r;
1872 } else {
1873 // remote -> local copy
1874 final Pipe pipe = Pipe.createRemoteToLocal();
1875
1876 Future<Integer> future = actAsync(new FileCallable<Integer>() {
1877 private static final long serialVersionUID = 1L;
1878 public Integer invoke(File f, VirtualChannel channel) throws IOException {
1879 try {
1880 return writeToTar(f,fileMask,excludes,TarCompression.GZIP.compress(pipe.getOut()));
1881 } finally {
1882 pipe.getOut().close();
1883 }
1884 }
1885 });
1886 try {
1887 readFromTar(remote+'/'+fileMask,new File(target.remote),TarCompression.GZIP.extract(pipe.getIn()));
1888 } catch (IOException e) {// BuildException or IOException
1889 try {
1890 future.get(3,TimeUnit.SECONDS);
1891 throw e; // the remote side completed successfully, so the error must be local
1892 } catch (ExecutionException x) {
1893 // report both errors
1894 throw new IOException2(Functions.printThrowable(e),x);
1895 } catch (TimeoutException _) {
1896 // remote is hanging
1897 throw e;
1898 }
1899 }
1900 try {
1901 return future.get();
1902 } catch (ExecutionException e) {
1903 throw new IOException2(e);
1904 }
1905 }
1906 }
1907
1908
1909 /**
1910 * Writes files in 'this' directory to a tar stream.
1911 *
1912 * @param glob
1913 * Ant file pattern mask, like "**&#x2F;*.java".
1914 */
1915 public int tar(OutputStream out, final String glob) throws IOException, InterruptedException {
1916 return archive(ArchiverFactory.TAR, out, glob);
1917 }
1918
1919 public int tar(OutputStream out, FileFilter filter) throws IOException, InterruptedException {
1920 return archive(ArchiverFactory.TAR, out, filter);
1921 }
1922
1923 /**
1924 * Uses the given scanner on 'this' directory to list up files and then archive it to a tar stream.
1925 */
1926 public int tar(OutputStream out, DirScanner scanner) throws IOException, InterruptedException {
1927 return archive(ArchiverFactory.TAR, out, scanner);
1928 }
1929
1930 /**
1931 * Writes to a tar stream and stores obtained files to the base dir.
1932 *
1933 * @return
1934 * number of files/directories that are written.
1935 */
1936 private static Integer writeToTar(File baseDir, String fileMask, String excludes, OutputStream out) throws IOException {
1937 Archiver tw = ArchiverFactory.TAR.create(out);
1938 try {
1939 new DirScanner.Glob(fileMask,excludes).scan(baseDir,tw);
1940 } finally {
1941 tw.close();
1942 }
1943 return tw.countEntries();
1944 }
1945
1946 /**
1947 * Reads from a tar stream and stores obtained files to the base dir.
1948 */
1949 private static void readFromTar(String name, File baseDir, InputStream in) throws IOException {
1950 TarInputStream t = new TarInputStream(in);
1951 try {
1952 TarEntry te;
1953 while ((te = t.getNextEntry()) != null) {
1954 File f = new File(baseDir,te.getName());
1955 if(te.isDirectory()) {
1956 f.mkdirs();
1957 } else {
1958 File parent = f.getParentFile();
1959 if (parent != null) parent.mkdirs();
1960
1961 byte linkFlag = (Byte) LINKFLAG_FIELD.get(te);
1962 if (linkFlag==TarEntry.LF_SYMLINK) {
1963 new FilePath(f).symlinkTo(te.getLinkName(), TaskListener.NULL);
1964 } else {
1965 IOUtils.copy(t,f);
1966
1967 f.setLastModified(te.getModTime().getTime());
1968 int mode = te.getMode()&0777;
1969 if(mode!=0 && !Functions.isWindows()) // be defensive
1970 _chmod(f,mode);
1971 }
1972 }
1973 }
1974 } catch(IOException e) {
1975 throw new IOException2("Failed to extract "+name,e);
1976 } catch (InterruptedException e) {
1977 Thread.currentThread().interrupt(); // process this later
1978 throw new IOException2("Failed to extract "+name,e);
1979 } catch (IllegalAccessException e) {
1980 throw new IOException2("Failed to extract "+name,e);
1981 } finally {
1982 t.close();
1983 }
1984 }
1985
1986 /**
1987 * Creates a {@link Launcher} for starting processes on the node
1988 * that has this file.
1989 * @since 1.89
1990 */
1991 public Launcher createLauncher(TaskListener listener) throws IOException, InterruptedException {
1992 if(channel==null)
1993 return new LocalLauncher(listener);
1994 else
1995 return new RemoteLauncher(listener,channel,channel.call(new IsUnix()));
1996 }
1997
1998 private static final class IsUnix implements Callable<Boolean,IOException> {
1999 public Boolean call() throws IOException {
2000 return File.pathSeparatorChar==':';
2001 }
2002 private static final long serialVersionUID = 1L;
2003 }
2004
2005 /**
2006 * Validates the ant file mask (like "foo/bar/*.txt, zot/*.jar")
2007 * against this directory, and try to point out the problem.
2008 *
2009 * <p>
2010 * This is useful in conjunction with {@link FormValidation}.
2011 *
2012 * @return
2013 * null if no error was found. Otherwise returns a human readable error message.
2014 * @since 1.90
2015 * @see #validateFileMask(FilePath, String)
2016 */
2017 public String validateAntFileMask(final String fileMasks) throws IOException, InterruptedException {
2018 return validateAntFileMask(fileMasks, Integer.MAX_VALUE);
2019 }
2020
2021 /**
2022 * Like {@link #validateAntFileMask(String)} but performing only a bounded number of operations.
2023 * <p>Whereas the unbounded overload is appropriate for calling from cancelable, long-running tasks such as build steps,
2024 * this overload should be used when an answer is needed quickly, such as for {@link #validateFileMask(String)}
2025 * or anything else returning {@link FormValidation}.
2026 * <p>If a positive match is found, {@code null} is returned immediately.
2027 * A message is returned in case the file pattern can definitely be determined to not match anything in the directory within the alloted time.
2028 * If the time runs out without finding a match but without ruling out the possibility that there might be one, {@link InterruptedException} is thrown,
2029 * in which case the calling code should give the user the benefit of the doubt and use {@link hudson.util.FormValidation.Kind#OK} (with or without a message).
2030 * @param bound a maximum number of negative operations (deliberately left vague) to perform before giving up on a precise answer; 10_000 is a reasonable pick
2031 * @throws InterruptedException not only in case of a channel failure, but also if too many operations were performed without finding any matches
2032 * @since 1.484
2033 */
2034 public String validateAntFileMask(final String fileMasks, final int bound) throws IOException, InterruptedException {
2035 return act(new FileCallable<String>() {
2036 private static final long serialVersionUID = 1;
2037 public String invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
2038 if(fileMasks.startsWith("~"))
2039 return Messages.FilePath_TildaDoesntWork();
2040
2041 StringTokenizer tokens = new StringTokenizer(fileMasks,",");
2042
2043 while(tokens.hasMoreTokens()) {
2044 final String fileMask = tokens.nextToken().trim();
2045 if(hasMatch(dir,fileMask))
2046 continue; // no error on this portion
2047
2048 // in 1.172 we introduced an incompatible change to stop using ' ' as the separator
2049 // so see if we can match by using ' ' as the separator
2050 if(fileMask.contains(" ")) {
2051 boolean matched = true;
2052 for (String token : Util.tokenize(fileMask))
2053 matched &= hasMatch(dir,token);
2054 if(matched)
2055 return Messages.FilePath_validateAntFileMask_whitespaceSeprator();
2056 }
2057
2058 // a common mistake is to assume the wrong base dir, and there are two variations
2059 // to this: (1) the user gave us aa/bb/cc/dd where cc/dd was correct
2060 // and (2) the user gave us cc/dd where aa/bb/cc/dd was correct.
2061
2062 {// check the (1) above first
2063 String f=fileMask;
2064 while(true) {
2065 int idx = findSeparator(f);
2066 if(idx==-1) break;
2067 f=f.substring(idx+1);
2068
2069 if(hasMatch(dir,f))
2070 return Messages.FilePath_validateAntFileMask_doesntMatchAndSuggest(fileMask,f);
2071 }
2072 }
2073
2074 {// check the (2) above next as this is more expensive.
2075 // Try prepending "**/" to see if that results in a match
2076 FileSet fs = Util.createFileSet(dir,"**/"+fileMask);
2077 DirectoryScanner ds = fs.getDirectoryScanner(new Project());
2078 if(ds.getIncludedFilesCount()!=0) {
2079 // try shorter name first so that the suggestion results in least amount of changes
2080 String[] names = ds.getIncludedFiles();
2081 Arrays.sort(names,SHORTER_STRING_FIRST);
2082 for( String f : names) {
2083 // now we want to decompose f to the leading portion that matched "**"
2084 // and the trailing portion that matched the file mask, so that
2085 // we can suggest the user error.
2086 //
2087 // this is not a very efficient/clever way to do it, but it's relatively simple
2088
2089 String prefix="";
2090 while(true) {
2091 int idx = findSeparator(f);
2092 if(idx==-1) break;
2093
2094 prefix+=f.substring(0,idx)+'/';
2095 f=f.substring(idx+1);
2096 if(hasMatch(dir,prefix+fileMask))
2097 return Messages.FilePath_validateAntFileMask_doesntMatchAndSuggest(fileMask, prefix+fileMask);
2098 }
2099 }
2100 }
2101 }
2102
2103 {// finally, see if we can identify any sub portion that's valid. Otherwise bail out
2104 String previous = null;
2105 String pattern = fileMask;
2106
2107 while(true) {
2108 if(hasMatch(dir,pattern)) {
2109 // found a match
2110 if(previous==null)
2111 return Messages.FilePath_validateAntFileMask_portionMatchAndSuggest(fileMask,pattern);
2112 else
2113 return Messages.FilePath_validateAntFileMask_portionMatchButPreviousNotMatchAndSuggest(fileMask,pattern,previous);
2114 }
2115
2116 int idx = findSeparator(pattern);
2117 if(idx<0) {// no more path component left to go back
2118 if(pattern.equals(fileMask))
2119 return Messages.FilePath_validateAntFileMask_doesntMatchAnything(fileMask);
2120 else
2121 return Messages.FilePath_validateAntFileMask_doesntMatchAnythingAndSuggest(fileMask,pattern);
2122 }
2123
2124 // cut off the trailing component and try again
2125 previous = pattern;
2126 pattern = pattern.substring(0,idx);
2127 }
2128 }
2129 }
2130
2131 return null; // no error
2132 }
2133
2134 private boolean hasMatch(File dir, String pattern) throws InterruptedException {
2135 class Cancel extends RuntimeException {}
2136 DirectoryScanner ds = bound == Integer.MAX_VALUE ? new DirectoryScanner() : new DirectoryScanner() {
2137 int ticks;
2138 @Override public synchronized boolean isCaseSensitive() {
2139 if (!filesIncluded.isEmpty() || !dirsIncluded.isEmpty() || ticks++ > bound) {
2140 throw new Cancel();
2141 }
2142 return super.isCaseSensitive();
2143 }
2144 };
2145 ds.setBasedir(dir);
2146 ds.setIncludes(new String[] {pattern});
2147 try {
2148 ds.scan();
2149 } catch (Cancel c) {
2150 if (ds.getIncludedFilesCount()!=0 || ds.getIncludedDirsCount()!=0) {
2151 return true;
2152 } else {
2153 throw new InterruptedException("no matches found within " + bound);
2154 }
2155 }
2156 return ds.getIncludedFilesCount()!=0 || ds.getIncludedDirsCount()!=0;
2157 }
2158
2159 /**
2160 * Finds the position of the first path separator.
2161 */
2162 private int findSeparator(String pattern) {
2163 int idx1 = pattern.indexOf('\\');
2164 int idx2 = pattern.indexOf('/');
2165 if(idx1==-1) return idx2;
2166 if(idx2==-1) return idx1;
2167 return Math.min(idx1,idx2);
2168 }
2169 });
2170 }
2171
2172 /**
2173 * Shortcut for {@link #validateFileMask(String)} in case the left-hand side can be null.
2174 */
2175 public static FormValidation validateFileMask(FilePath pathOrNull, String value) throws IOException {
2176 if(pathOrNull==null) return FormValidation.ok();
2177 return pathOrNull.validateFileMask(value);
2178 }
2179
2180 /**
2181 * Short for {@code validateFileMask(value,true)}
2182 */
2183 public FormValidation validateFileMask(String value) throws IOException {
2184 return validateFileMask(value,true);
2185 }
2186
2187 /**
2188 * Checks the GLOB-style file mask. See {@link #validateAntFileMask(String)}.
2189 * Requires configure permission on ancestor AbstractProject object in request,
2190 * or admin permission if no such ancestor is found.
2191 * @since 1.294
2192 */
2193 public FormValidation validateFileMask(String value, boolean errorIfNotExist) throws IOException {
2194 checkPermissionForValidate();
2195
2196 value = fixEmpty(value);
2197 if(value==null)
2198 return FormValidation.ok();
2199
2200 try {
2201 if(!exists()) // no workspace. can't check
2202 return FormValidation.ok();
2203
2204 String msg = validateAntFileMask(value, 10000);
2205 if(errorIfNotExist) return FormValidation.error(msg);
2206 else return FormValidation.warning(msg);
2207 } catch (InterruptedException e) {
2208 return FormValidation.ok(Messages.FilePath_did_not_manage_to_validate_may_be_too_sl(value));
2209 }
2210 }
2211
2212 /**
2213 * Validates a relative file path from this {@link FilePath}.
2214 * Requires configure permission on ancestor AbstractProject object in request,
2215 * or admin permission if no such ancestor is found.
2216 *
2217 * @param value
2218 * The relative path being validated.
2219 * @param errorIfNotExist
2220 * If true, report an error if the given relative path doesn't exist. Otherwise it's a warning.
2221 * @param expectingFile
2222 * If true, we expect the relative path to point to a file.
2223 * Otherwise, the relative path is expected to be pointing to a directory.
2224 */
2225 public FormValidation validateRelativePath(String value, boolean errorIfNotExist, boolean expectingFile) throws IOException {
2226 checkPermissionForValidate();
2227
2228 value = fixEmpty(value);
2229
2230 // none entered yet, or something is seriously wrong
2231 if(value==null) return FormValidation.ok();
2232
2233 // a common mistake is to use wildcard
2234 if(value.contains("*")) return FormValidation.error(Messages.FilePath_validateRelativePath_wildcardNotAllowed());
2235
2236 try {
2237 if(!exists()) // no base directory. can't check
2238 return FormValidation.ok();
2239
2240 FilePath path = child(value);
2241 if(path.exists()) {
2242 if (expectingFile) {
2243 if(!path.isDirectory())
2244 return FormValidation.ok();
2245 else
2246 return FormValidation.error(Messages.FilePath_validateRelativePath_notFile(value));
2247 } else {
2248 if(path.isDirectory())
2249 return FormValidation.ok();
2250 else
2251 return FormValidation.error(Messages.FilePath_validateRelativePath_notDirectory(value));
2252 }
2253 }
2254
2255 String msg = expectingFile ? Messages.FilePath_validateRelativePath_noSuchFile(value) :
2256 Messages.FilePath_validateRelativePath_noSuchDirectory(value);
2257 if(errorIfNotExist) return FormValidation.error(msg);
2258 else return FormValidation.warning(msg);
2259 } catch (InterruptedException e) {
2260 return FormValidation.ok();
2261 }
2262 }
2263
2264 private static void checkPermissionForValidate() {
2265 AccessControlled subject = Stapler.getCurrentRequest().findAncestorObject(AbstractProject.class);
2266 if (subject == null)
2267 Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
2268 else
2269 subject.checkPermission(Item.CONFIGURE);
2270 }
2271
2272 /**
2273 * A convenience method over {@link #validateRelativePath(String, boolean, boolean)}.
2274 */
2275 public FormValidation validateRelativeDirectory(String value, boolean errorIfNotExist) throws IOException {
2276 return validateRelativePath(value,errorIfNotExist,false);
2277 }
2278
2279 public FormValidation validateRelativeDirectory(String value) throws IOException {
2280 return validateRelativeDirectory(value,true);
2281 }
2282
2283 @Deprecated @Override
2284 public String toString() {
2285 // to make writing JSPs easily, return local
2286 return remote;
2287 }
2288
2289 public VirtualChannel getChannel() {
2290 if(channel!=null) return channel;
2291 else return Jenkins.MasterComputer.localChannel;
2292 }
2293
2294 /**
2295 * Returns true if this {@link FilePath} represents a remote file.
2296 */
2297 public boolean isRemote() {
2298 return channel!=null;
2299 }
2300
2301 private void writeObject(ObjectOutputStream oos) throws IOException {
2302 Channel target = Channel.current();
2303
2304 if(channel!=null && channel!=target)
2305 throw new IllegalStateException("Can't send a remote FilePath to a different remote channel");
2306
2307 oos.defaultWriteObject();
2308 oos.writeBoolean(channel==null);
2309 }
2310
2311 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
2312 Channel channel = Channel.current();
2313 assert channel!=null;
2314
2315 ois.defaultReadObject();
2316 if(ois.readBoolean()) {
2317 this.channel = channel;
2318 } else {
2319 this.channel = null;
2320 }
2321 }
2322
2323 private static final long serialVersionUID = 1L;
2324
2325 public static int SIDE_BUFFER_SIZE = 1024;
2326
2327 private static final Logger LOGGER = Logger.getLogger(FilePath.class.getName());
2328
2329 /**
2330 * Adapts {@link FileCallable} to {@link Callable}.
2331 */
2332 private class FileCallableWrapper<T> implements DelegatingCallable<T,IOException> {
2333 private final FileCallable<T> callable;
2334 private transient ClassLoader classLoader;
2335
2336 public FileCallableWrapper(FileCallable<T> callable) {
2337 this.callable = callable;
2338 this.classLoader = callable.getClass().getClassLoader();
2339 }
2340
2341 private FileCallableWrapper(FileCallable<T> callable, ClassLoader classLoader) {
2342 this.callable = callable;
2343 this.classLoader = classLoader;
2344 }
2345
2346 public T call() throws IOException {
2347 try {
2348 return callable.invoke(new File(remote), Channel.current());
2349 } catch (InterruptedException e) {
2350 throw new TunneledInterruptedException(e);
2351 }
2352 }
2353
2354 public ClassLoader getClassLoader() {
2355 return classLoader;
2356 }
2357
2358 private static final long serialVersionUID = 1L;
2359 }
2360
2361 /**
2362 * Used to tunnel {@link InterruptedException} over a Java signature that only allows {@link IOException}
2363 */
2364 private static class TunneledInterruptedException extends IOException2 {
2365 private TunneledInterruptedException(InterruptedException cause) {
2366 super(cause);
2367 }
2368 private static final long serialVersionUID = 1L;
2369 }
2370
2371 private static final Comparator<String> SHORTER_STRING_FIRST = new Comparator<String>() {
2372 public int compare(String o1, String o2) {
2373 return o1.length()-o2.length();
2374 }
2375 };
2376
2377 private static final Field LINKFLAG_FIELD = getTarEntryLinkFlagField();
2378
2379 private static Field getTarEntryLinkFlagField() {
2380 try {
2381 Field f = TarEntry.class.getDeclaredField("linkFlag");
2382 f.setAccessible(true);
2383 return f;
2384 } catch (SecurityException e) {
2385 throw new AssertionError(e);
2386 } catch (NoSuchFieldException e) {
2387 throw new AssertionError(e);
2388 }
2389 }
2390 }
2391