FilePath.java 102.5 KB
Newer Older
K
kohsuke 已提交
1 2 3
/*
 * The MIT License
 * 
4
 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
R
rseguy 已提交
5 6
 * Eric Lefevre-Ardant, Erik Ramfelt, Michael B. Donohue, Alan Harder,
 * Manufacture Francaise des Pneumatiques Michelin, Romain Seguy
K
kohsuke 已提交
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
K
kohsuke 已提交
26 27
package hudson;

28 29 30
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.GZIPOutputStream;
import com.sun.jna.Native;
31 32
import hudson.Launcher.LocalLauncher;
import hudson.Launcher.RemoteLauncher;
K
kohsuke 已提交
33 34
import hudson.model.AbstractProject;
import hudson.model.Item;
35 36 37
import hudson.model.TaskListener;
import hudson.org.apache.tools.tar.TarInputStream;
import hudson.os.PosixException;
K
kohsuke 已提交
38 39
import hudson.remoting.Callable;
import hudson.remoting.Channel;
40
import hudson.remoting.DelegatingCallable;
41
import hudson.remoting.Future;
K
kohsuke 已提交
42
import hudson.remoting.Pipe;
43
import hudson.remoting.RemoteInputStream;
K
kohsuke 已提交
44 45
import hudson.remoting.RemoteOutputStream;
import hudson.remoting.VirtualChannel;
46
import hudson.remoting.Which;
47
import hudson.security.AccessControlled;
48
import hudson.util.DirScanner;
49
import hudson.util.FileVisitor;
K
kohsuke 已提交
50
import hudson.util.FormValidation;
51 52
import hudson.util.HeadBufferingStream;
import hudson.util.IOException2;
K
kohsuke 已提交
53
import hudson.util.IOUtils;
54 55
import hudson.util.io.Archiver;
import hudson.util.io.ArchiverFactory;
56
import jenkins.FilePathFilter;
57 58
import jenkins.MasterToSlaveFileCallable;
import jenkins.SlaveToMasterFileCallable;
59
import jenkins.model.Jenkins;
60
import jenkins.security.MasterToSlaveCallable;
61
import jenkins.util.VirtualFile;
62 63
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.input.CountingInputStream;
K
kohsuke 已提交
64
import org.apache.tools.ant.DirectoryScanner;
65
import org.apache.tools.ant.Project;
66
import org.apache.tools.ant.taskdefs.Chmod;
K
kohsuke 已提交
67
import org.apache.tools.ant.types.FileSet;
68
import org.apache.tools.tar.TarEntry;
69 70
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
71
import org.jenkinsci.remoting.RoleChecker;
72
import org.jenkinsci.remoting.RoleSensitive;
K
kohsuke 已提交
73
import org.kohsuke.stapler.Stapler;
74

75 76
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
77
import java.io.BufferedOutputStream;
K
kohsuke 已提交
78
import java.io.File;
K
kohsuke 已提交
79 80 81 82
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
K
kohsuke 已提交
83
import java.io.IOException;
K
kohsuke 已提交
84
import java.io.InputStream;
K
Kohsuke Kawaguchi 已提交
85
import java.io.InterruptedIOException;
K
kohsuke 已提交
86 87 88
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
89
import java.io.OutputStreamWriter;
K
kohsuke 已提交
90 91
import java.io.Serializable;
import java.io.Writer;
K
Kohsuke Kawaguchi 已提交
92
import java.lang.reflect.Field;
93
import java.net.HttpURLConnection;
94
import java.net.URI;
K
kohsuke 已提交
95
import java.net.URL;
K
kohsuke 已提交
96
import java.net.URLConnection;
K
kohsuke 已提交
97
import java.util.ArrayList;
98 99
import java.util.Arrays;
import java.util.Comparator;
100 101 102 103
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
104
import java.util.concurrent.ExecutionException;
105 106
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
107
import java.util.concurrent.atomic.AtomicInteger;
108
import java.util.logging.Level;
R
rseguy 已提交
109
import java.util.logging.Logger;
110 111
import java.util.regex.Matcher;
import java.util.regex.Pattern;
K
kohsuke 已提交
112

113 114 115
import static hudson.FilePath.TarCompression.*;
import static hudson.Util.*;
import static hudson.util.jna.GNUCLibrary.*;
116
        
K
kohsuke 已提交
117
/**
K
kohsuke 已提交
118
 * {@link File} like object with remoting support.
K
kohsuke 已提交
119 120
 *
 * <p>
K
kohsuke 已提交
121 122 123 124 125 126 127 128 129 130 131 132 133
 * Unlike {@link File}, which always implies a file path on the current computer,
 * {@link FilePath} represents a file path on a specific slave or the master.
 *
 * Despite that, {@link FilePath} can be used much like {@link File}. It exposes
 * a bunch of operations (and we should add more operations as long as they are
 * generally useful), and when invoked against a file on a remote node, {@link FilePath}
 * executes the necessary code remotely, thereby providing semi-transparent file
 * operations.
 *
 * <h2>Using {@link FilePath} smartly</h2>
 * <p>
 * The transparency makes it easy to write plugins without worrying too much about
 * remoting, by making it works like NFS, where remoting happens at the file-system
M
mindless 已提交
134
 * layer.
K
kohsuke 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
 *
 * <p>
 * But one should note that such use of remoting may not be optional. Sometimes,
 * it makes more sense to move some computation closer to the data, as opposed to
 * move the data to the computation. For example, if you are just computing a MD5
 * digest of a file, then it would make sense to do the digest on the host where
 * the file is located, as opposed to send the whole data to the master and do MD5
 * digesting there.
 *
 * <p>
 * {@link FilePath} supports this "code migration" by in the
 * {@link #act(FileCallable)} method. One can pass in a custom implementation
 * of {@link FileCallable}, to be executed on the node where the data is located.
 * The following code shows the example:
 *
 * <pre>
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
 * void someMethod(FilePath file) {
 *     // make 'file' a fresh empty directory.
 *     file.act(new Freshen());
 * }
 * // if 'file' is on a different node, this FileCallable will
 * // be transferred to that node and executed there.
 * private static final class Freshen implements FileCallable&lt;Void> {
 *     private static final long serialVersionUID = 1;
 *     &#64;Override public Void invoke(File f, VirtualChannel channel) {
 *         // f and file represent the same thing
 *         f.deleteContents();
 *         f.mkdirs();
 *         return null;
 *     }
 * }
K
kohsuke 已提交
166
 * </pre>
K
kohsuke 已提交
167 168
 *
 * <p>
K
kohsuke 已提交
169
 * When {@link FileCallable} is transfered to a remote node, it will be done so
E
elefevre 已提交
170
 * by using the same Java serialization scheme that the remoting module uses.
K
kohsuke 已提交
171 172 173 174 175 176 177
 * See {@link Channel} for more about this. 
 *
 * <p>
 * {@link FilePath} itself can be sent over to a remote node as a part of {@link Callable}
 * serialization. For example, sending a {@link FilePath} of a remote node to that
 * node causes {@link FilePath} to become "local". Similarly, sending a
 * {@link FilePath} that represents the local computer causes it to become "remote."
K
kohsuke 已提交
178 179
 *
 * @author Kohsuke Kawaguchi
K
Kohsuke Kawaguchi 已提交
180
 * @see VirtualFile
K
kohsuke 已提交
181
 */
K
kohsuke 已提交
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
public final class FilePath implements Serializable {
    /**
     * When this {@link FilePath} represents the remote path,
     * this field is always non-null on master (the field represents
     * the channel to the remote slave.) When transferred to a slave via remoting,
     * this field reverts back to null, since it's transient.
     *
     * When this {@link FilePath} represents a path on the master,
     * this field is null on master. When transferred to a slave via remoting,
     * this field becomes non-null, representing the {@link Channel}
     * back to the master.
     *
     * This is used to determine whether we are running on the master or the slave.
     */
    private transient VirtualChannel channel;

    // since the platform of the slave might be different, can't use java.io.File
K
kohsuke 已提交
199 200
    private final String remote;

201 202 203 204 205 206 207 208 209 210 211
    /**
     * If this {@link FilePath} is deserialized to handle file access request from a remote computer,
     * this field is set to the filter that performs access control.
     *
     * <p>
     * If null, no access control is needed.
     *
     * @see #filterNonNull()
     */
    private transient @Nullable FilePathFilter filter;

K
kohsuke 已提交
212 213 214 215 216 217 218
    /**
     * Creates a {@link FilePath} that represents a path on the given node.
     *
     * @param channel
     *      To create a path that represents a remote path, pass in a {@link Channel}
     *      that's connected to that machine. If null, that means the local file path.
     */
K
kohsuke 已提交
219
    public FilePath(VirtualChannel channel, String remote) {
220
        this.channel = channel == Jenkins.MasterComputer.localChannel ? null : channel;
221
        this.remote = normalize(remote);
K
kohsuke 已提交
222 223 224
    }

    /**
K
kohsuke 已提交
225 226 227 228 229
     * To create {@link FilePath} that represents a "local" path.
     *
     * <p>
     * A "local" path means a file path on the computer where the
     * constructor invocation happened.
K
kohsuke 已提交
230
     */
K
kohsuke 已提交
231 232
    public FilePath(File localPath) {
        this.channel = null;
233
        this.remote = normalize(localPath.getPath());
K
kohsuke 已提交
234 235
    }

K
kohsuke 已提交
236 237 238 239 240
    /**
     * Construct a path starting with a base location.
     * @param base starting point for resolution, and defines channel
     * @param rel a path which if relative will be resolved against base
     */
K
kohsuke 已提交
241
    public FilePath(FilePath base, String rel) {
K
kohsuke 已提交
242
        this.channel = base.channel;
243 244 245 246 247
        this.remote = normalize(resolvePathIfRelative(base, rel));
    }

    private String resolvePathIfRelative(FilePath base, String rel) {
        if(isAbsolute(rel)) return rel;
K
kohsuke 已提交
248
        if(base.isUnix()) {
249
            // shouldn't need this replace, but better safe than sorry
250
            return base.remote+'/'+rel.replace('\\','/');
K
kohsuke 已提交
251
        } else {
252 253
            // need this replace, see Slave.getWorkspaceFor and AbstractItem.getFullName, nested jobs on Windows
            // slaves will always have a rel containing at least one '/' character. JENKINS-13649
254
            return base.remote+'\\'+rel.replace('/','\\');
K
kohsuke 已提交
255 256 257
        }
    }

K
Kohsuke Kawaguchi 已提交
258 259 260
    /**
     * Is the given path name an absolute path?
     */
K
kohsuke 已提交
261 262 263 264
    private static boolean isAbsolute(String rel) {
        return rel.startsWith("/") || DRIVE_PATTERN.matcher(rel).matches();
    }

265 266
    private static final Pattern DRIVE_PATTERN = Pattern.compile("[A-Za-z]:[\\\\/].*"),
            ABSOLUTE_PREFIX_PATTERN = Pattern.compile("^(\\\\\\\\|(?:[A-Za-z]:)?[\\\\/])[\\\\/]*");
K
kohsuke 已提交
267

268 269 270 271 272
    /**
     * {@link File#getParent()} etc cannot handle ".." and "." in the path component very well,
     * so remove them.
     */
    private static String normalize(String path) {
273 274 275 276 277 278 279 280 281
        StringBuilder buf = new StringBuilder();
        // Check for prefix designating absolute path
        Matcher m = ABSOLUTE_PREFIX_PATTERN.matcher(path);
        if (m.find()) {
            buf.append(m.group(1));
            path = path.substring(m.end());
        }
        boolean isAbsolute = buf.length() > 0;
        // Split remaining path into tokens, trimming any duplicate or trailing separators
282
        List<String> tokens = new ArrayList<String>();
283 284 285 286 287 288 289 290 291 292 293
        int s = 0, end = path.length();
        for (int i = 0; i < end; i++) {
            char c = path.charAt(i);
            if (c == '/' || c == '\\') {
                tokens.add(path.substring(s, i));
                s = i;
                // Skip any extra separator chars
                while (++i < end && ((c = path.charAt(i)) == '/' || c == '\\')) { }
                // Add token for separator unless we reached the end
                if (i < end) tokens.add(path.substring(s, s+1));
                s = i;
294 295
            }
        }
296 297 298
        if (s < end) tokens.add(path.substring(s));
        // Look through tokens for "." or ".."
        for (int i = 0; i < tokens.size();) {
299 300 301
            String token = tokens.get(i);
            if (token.equals(".")) {
                tokens.remove(i);
302 303 304 305 306 307 308 309 310 311 312 313
                if (tokens.size() > 0)
                    tokens.remove(i > 0 ? i - 1 : i);
            } else if (token.equals("..")) {
                if (i == 0) {
                    // If absolute path, just remove: /../something
                    // If relative path, not collapsible so leave as-is
                    tokens.remove(0);
                    if (tokens.size() > 0) token += tokens.remove(0);
                    if (!isAbsolute) buf.append(token);
                } else {
                    // Normalize: remove something/.. plus separator before/after
                    i -= 2;
314
                    for (int j = 0; j < 3; j++) tokens.remove(i);
315 316 317
                    if (i > 0) tokens.remove(i-1);
                    else if (tokens.size() > 0) tokens.remove(0);
                }
318
            } else
319
                i += 2;
320
        }
321 322 323 324
        // Recombine tokens
        for (String token : tokens) buf.append(token);
        if (buf.length() == 0) buf.append('.');
        return buf.toString();
325 326
    }

K
kohsuke 已提交
327 328 329
    /**
     * Checks if the remote path is Unix.
     */
330
    boolean isUnix() {
331 332 333 334
        // if the path represents a local path, there' no need to guess.
        if(!isRemote())
            return File.pathSeparatorChar!=';';
            
335 336 337 338 339 340
        // note that we can't use the usual File.pathSeparator and etc., as the OS of
        // the machine where this code runs and the OS that this FilePath refers to may be different.

        // Windows absolute path is 'X:\...', so this is usually a good indication of Windows path
        if(remote.length()>3 && remote.charAt(1)==':' && remote.charAt(2)=='\\')
            return false;
K
kohsuke 已提交
341 342 343 344 345
        // Windows can handle '/' as a path separator but Unix can't,
        // so err on Unix side
        return remote.indexOf("\\")==-1;
    }

J
Jørgen P. Tjernø 已提交
346 347 348 349
    /**
     * Gets the full path of the file on the remote machine.
     *
     */
K
kohsuke 已提交
350 351 352 353
    public String getRemote() {
        return remote;
    }

354 355
    /**
     * Creates a zip file from this directory or a file and sends that to the given output stream.
356 357
     *
     * @deprecated as of 1.315. Use {@link #zip(OutputStream)} that has more consistent name.
358 359
     */
    public void createZipArchive(OutputStream os) throws IOException, InterruptedException {
360 361
        zip(os);
    }
362

363 364 365 366 367 368
    /**
     * Creates a zip file from this directory or a file and sends that to the given output stream.
     */
    public void zip(OutputStream os) throws IOException, InterruptedException {
        zip(os,(FileFilter)null);
    }
369

K
Kohsuke Kawaguchi 已提交
370 371 372 373 374 375 376 377 378
    public void zip(FilePath dst) throws IOException, InterruptedException {
        OutputStream os = dst.write();
        try {
            zip(os);
        } finally {
            os.close();
        }
    }
    
379 380 381 382 383 384 385 386 387 388
    /**
     * Creates a zip file from this directory by using the specified filter,
     * and sends the result to the given output stream.
     *
     * @param filter
     *      Must be serializable since it may be executed remotely. Can be null to add all files.
     *
     * @since 1.315
     */
    public void zip(OutputStream os, FileFilter filter) throws IOException, InterruptedException {
389
        archive(ArchiverFactory.ZIP,os,filter);
390 391
    }

K
kohsuke 已提交
392 393 394 395 396 397 398 399
    /**
     * Creates a zip file from this directory by only including the files that match the given glob.
     *
     * @param glob
     *      Ant style glob, like "**&#x2F;*.xml". If empty or null, this method
     *      works like {@link #createZipArchive(OutputStream)}
     *
     * @since 1.129
400 401
     * @deprecated as of 1.315
     *      Use {@link #zip(OutputStream,String)} that has more consistent name.
K
kohsuke 已提交
402 403
     */
    public void createZipArchive(OutputStream os, final String glob) throws IOException, InterruptedException {
404
        archive(ArchiverFactory.ZIP,os,glob);
405 406 407 408 409 410 411 412 413 414 415 416
    }

    /**
     * Creates a zip file from this directory by only including the files that match the given glob.
     *
     * @param glob
     *      Ant style glob, like "**&#x2F;*.xml". If empty or null, this method
     *      works like {@link #createZipArchive(OutputStream)}
     *
     * @since 1.315
     */
    public void zip(OutputStream os, final String glob) throws IOException, InterruptedException {
417
        archive(ArchiverFactory.ZIP,os,glob);
418 419
    }

420 421 422 423
    /**
     * Uses the given scanner on 'this' directory to list up files and then archive it to a zip stream.
     */
    public int zip(OutputStream out, DirScanner scanner) throws IOException, InterruptedException {
424
        return archive(ArchiverFactory.ZIP, out, scanner);
425 426
    }

427 428 429 430 431 432 433 434
    /**
     * Archives this directory into the specified archive format, to the given {@link OutputStream}, by using
     * {@link DirScanner} to choose what files to include.
     *
     * @return
     *      number of files/directories archived. This is only really useful to check for a situation where nothing
     *      is archived.
     */
435
    public int archive(final ArchiverFactory factory, OutputStream os, final DirScanner scanner) throws IOException, InterruptedException {
K
kohsuke 已提交
436
        final OutputStream out = (channel!=null)?new RemoteOutputStream(os):os;
437
        return act(new SecureFileCallable<Integer>() {
438 439 440
            public Integer invoke(File f, VirtualChannel channel) throws IOException {
                Archiver a = factory.create(out);
                try {
441
                    scanner.scan(f,reading(a));
442 443
                } finally {
                    a.close();
K
kohsuke 已提交
444
                }
445
                return a.countEntries();
K
kohsuke 已提交
446 447 448 449 450 451
            }

            private static final long serialVersionUID = 1L;
        });
    }

K
Kohsuke Kawaguchi 已提交
452
    public int archive(final ArchiverFactory factory, OutputStream os, final FileFilter filter) throws IOException, InterruptedException {
453 454 455
        return archive(factory,os,new DirScanner.Filter(filter));
    }

K
Kohsuke Kawaguchi 已提交
456
    public int archive(final ArchiverFactory factory, OutputStream os, final String glob) throws IOException, InterruptedException {
457 458 459
        return archive(factory,os,new DirScanner.Glob(glob,null));
    }

K
kohsuke 已提交
460 461 462 463 464 465
    /**
     * When this {@link FilePath} represents a zip file, extracts that zip file.
     *
     * @param target
     *      Target directory to expand files to. All the necessary directories will be created.
     * @since 1.248
K
kohsuke 已提交
466
     * @see #unzipFrom(InputStream)
K
kohsuke 已提交
467
     */
K
kohsuke 已提交
468
    public void unzip(final FilePath target) throws IOException, InterruptedException {
469
        target.act(new SecureFileCallable<Void>() {
470

471
            public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
472 473 474
                if (FilePath.this.isRemote())
                    unzip(dir, FilePath.this.read()); // use streams
                else
475
                    unzip(dir, reading(new File(FilePath.this.getRemote()))); // shortcut to local file
K
kohsuke 已提交
476 477
                return null;
            }
K
kohsuke 已提交
478 479 480
            private static final long serialVersionUID = 1L;
        });
    }
K
kohsuke 已提交
481

K
kohsuke 已提交
482 483 484 485 486 487 488 489 490 491 492
    /**
     * When this {@link FilePath} represents a tar file, extracts that tar file.
     *
     * @param target
     *      Target directory to expand files to. All the necessary directories will be created.
     * @param compression
     *      Compression mode of this tar file.
     * @since 1.292
     * @see #untarFrom(InputStream, TarCompression)
     */
    public void untar(final FilePath target, final TarCompression compression) throws IOException, InterruptedException {
493
        target.act(new SecureFileCallable<Void>() {
494
            public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
K
kohsuke 已提交
495 496 497 498 499 500 501
                readFromTar(FilePath.this.getName(),dir,compression.extract(FilePath.this.read()));
                return null;
            }
            private static final long serialVersionUID = 1L;
        });
    }

K
kohsuke 已提交
502 503 504 505 506 507
    /**
     * Reads the given InputStream as a zip file and extracts it into this directory.
     *
     * @param _in
     *      The stream will be closed by this method after it's fully read.
     * @since 1.283
K
kohsuke 已提交
508
     * @see #unzip(FilePath)
K
kohsuke 已提交
509 510 511
     */
    public void unzipFrom(InputStream _in) throws IOException, InterruptedException {
        final InputStream in = new RemoteInputStream(_in);
512
        act(new SecureFileCallable<Void>() {
K
kohsuke 已提交
513 514 515 516
            public Void invoke(File dir, VirtualChannel channel) throws IOException {
                unzip(dir, in);
                return null;
            }
K
kohsuke 已提交
517 518 519 520
            private static final long serialVersionUID = 1L;
        });
    }

521
    private void unzip(File dir, InputStream in) throws IOException {
522 523
        File tmpFile = File.createTempFile("tmpzip", null); // uses java.io.tmpdir
        try {
524
            // TODO why does this not simply use ZipInputStream?
525 526 527 528 529 530 531 532
            IOUtils.copy(in, tmpFile);
            unzip(dir,tmpFile);
        }
        finally {
            tmpFile.delete();
        }
    }

533
    private void unzip(File dir, File zipFile) throws IOException {
K
kohsuke 已提交
534
        dir = dir.getAbsoluteFile();    // without absolutization, getParentFile below seems to fail
535
        ZipFile zip = new ZipFile(zipFile);
C
Christoph Kutzinski 已提交
536
        @SuppressWarnings("unchecked")
537
        Enumeration<ZipEntry> entries = zip.getEntries();
K
kohsuke 已提交
538 539

        try {
540 541 542 543
            while (entries.hasMoreElements()) {
                ZipEntry e = entries.nextElement();
                File f = new File(dir, e.getName());
                if (e.isDirectory()) {
544
                    mkdirs(f);
K
kohsuke 已提交
545 546
                } else {
                    File p = f.getParentFile();
547
                    if (p != null) {
548
                        mkdirs(p);
549
                    }
550
                    IOUtils.copy(zip.getInputStream(e), writing(f));
551 552
                    try {
                        FilePath target = new FilePath(f);
553 554 555
                        int mode = e.getUnixMode();
                        if (mode!=0)    // Ant returns 0 if the archive doesn't record the access mode
                            target.chmod(mode);
556 557 558
                    } catch (InterruptedException ex) {
                        LOGGER.log(Level.WARNING, "unable to set permissions", ex);
                    }
559
                    f.setLastModified(e.getTime());
K
kohsuke 已提交
560 561 562 563 564 565 566
                }
            }
        } finally {
            zip.close();
        }
    }

K
kohsuke 已提交
567 568 569 570
    /**
     * Absolutizes this {@link FilePath} and returns the new one.
     */
    public FilePath absolutize() throws IOException, InterruptedException {
571
        return new FilePath(channel,act(new SecureFileCallable<String>() {
572
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
573 574 575 576 577 578
            public String invoke(File f, VirtualChannel channel) throws IOException {
                return f.getAbsolutePath();
            }
        }));
    }

K
Kohsuke Kawaguchi 已提交
579 580 581 582 583 584 585 586 587 588
    /**
     * Creates a symlink to the specified target.
     *
     * @param target
     *      The file that the symlink should point to.
     * @param listener
     *      If symlink creation requires a help of an external process, the error will be reported here.
     * @since 1.456
     */
    public void symlinkTo(final String target, final TaskListener listener) throws IOException, InterruptedException {
589
        act(new SecureFileCallable<Void>() {
590
            private static final long serialVersionUID = 1L;
K
Kohsuke Kawaguchi 已提交
591
            public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
592
                writing(f);
K
Kohsuke Kawaguchi 已提交
593 594 595 596 597 598 599 600 601 602 603 604 605 606
                Util.createSymlink(f.getParentFile(),target,f.getName(),listener);
                return null;
            }
        });
    }
    
    /**
     * Resolves symlink, if the given file is a symlink. Otherwise return null.
     * <p>
     * If the resolution fails, report an error.
     *
     * @since 1.456
     */
    public String readLink() throws IOException, InterruptedException {
607
        return act(new SecureFileCallable<String>() {
608
            private static final long serialVersionUID = 1L;
K
Kohsuke Kawaguchi 已提交
609
            public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
610
                return Util.resolveSymlink(reading(f));
K
Kohsuke Kawaguchi 已提交
611 612 613 614
            }
        });
    }

K
kohsuke 已提交
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        FilePath that = (FilePath) o;

        if (channel != null ? !channel.equals(that.channel) : that.channel != null) return false;
        return remote.equals(that.remote);

    }

    @Override
    public int hashCode() {
        return 31 * (channel != null ? channel.hashCode() : 0) + remote.hashCode();
    }
    
K
kohsuke 已提交
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
    /**
     * Supported tar file compression methods.
     */
    public enum TarCompression {
        NONE {
            public InputStream extract(InputStream in) {
                return in;
            }
            public OutputStream compress(OutputStream out) {
                return out;
            }
        },
        GZIP {
            public InputStream extract(InputStream _in) throws IOException {
                HeadBufferingStream in = new HeadBufferingStream(_in,SIDE_BUFFER_SIZE);
                try {
648
                    return new GZIPInputStream(in, 8192, true);
K
kohsuke 已提交
649 650 651 652 653 654 655
                } catch (IOException e) {
                    // various people reported "java.io.IOException: Not in GZIP format" here, so diagnose this problem better
                    in.fillSide();
                    throw new IOException2(e.getMessage()+"\nstream="+Util.toHexString(in.getSideBuffer()),e);
                }
            }
            public OutputStream compress(OutputStream out) throws IOException {
656 657 658 659 660 661
                return new GZIPOutputStream(new BufferedOutputStream(out),
                        // TODO JENKINS-19473 workaround; replace when jzlib fixed
                        new com.jcraft.jzlib.Deflater(6, 15+16, 9),   // use 9 for memLevel
                        512,
                        true
                );
K
kohsuke 已提交
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
            }
        };

        public abstract InputStream extract(InputStream in) throws IOException;
        public abstract OutputStream compress(OutputStream in) throws IOException;
    }

    /**
     * Reads the given InputStream as a tar file and extracts it into this directory.
     *
     * @param _in
     *      The stream will be closed by this method after it's fully read.
     * @param compression
     *      The compression method in use.
     * @since 1.292
     */
    public void untarFrom(InputStream _in, final TarCompression compression) throws IOException, InterruptedException {
        try {
            final InputStream in = new RemoteInputStream(_in);
681
            act(new SecureFileCallable<Void>() {
K
kohsuke 已提交
682
                public Void invoke(File dir, VirtualChannel channel) throws IOException {
683
                    readFromTar("input stream",dir, compression.extract(in));
K
kohsuke 已提交
684 685 686 687 688 689 690 691 692
                    return null;
                }
                private static final long serialVersionUID = 1L;
            });
        } finally {
            IOUtils.closeQuietly(_in);
        }
    }

K
kohsuke 已提交
693
    /**
K
kohsuke 已提交
694
     * Given a tgz/zip file, extracts it to the given target directory, if necessary.
K
kohsuke 已提交
695 696 697 698 699 700
     *
     * <p>
     * This method is a convenience method designed for installing a binary package to a location
     * that supports upgrade and downgrade. Specifically,
     *
     * <ul>
K
kohsuke 已提交
701
     * <li>If the target directory doesn't exist {@linkplain #mkdirs() it'll be created}.
K
kohsuke 已提交
702
     * <li>The timestamp of the .tgz file is left in the installation directory upon extraction.
K
kohsuke 已提交
703 704
     * <li>If the timestamp left in the directory doesn't match with the timestamp of the current archive file,
     *     the directory contents will be discarded and the archive file will be re-extracted.
K
kohsuke 已提交
705
     * <li>If the connection is refused but the target directory already exists, it is left alone.
K
kohsuke 已提交
706 707
     * </ul>
     *
K
kohsuke 已提交
708 709
     * @param archive
     *      The resource that represents the tgz/zip file. This URL must support the "Last-Modified" header.
K
kohsuke 已提交
710 711 712 713 714 715 716 717 718
     *      (Most common usage is to get this from {@link ClassLoader#getResource(String)})
     * @param listener
     *      If non-null, a message will be printed to this listener once this method decides to
     *      extract an archive.
     * @return
     *      true if the archive was extracted. false if the extraction was skipped because the target directory
     *      was considered up to date.
     * @since 1.299
     */
K
kohsuke 已提交
719
    public boolean installIfNecessaryFrom(URL archive, TaskListener listener, String message) throws IOException, InterruptedException {
K
kohsuke 已提交
720
        try {
721
            FilePath timestamp = this.child(".timestamp");
722 723
            URLConnection con;
            try {
724
                con = ProxyConfiguration.open(archive);
725 726 727
                if (timestamp.exists()) {
                    con.setIfModifiedSince(timestamp.lastModified());
                }
728 729 730 731 732 733 734 735 736 737
                con.connect();
            } catch (IOException x) {
                if (this.exists()) {
                    // Cannot connect now, so assume whatever was last unpacked is still OK.
                    if (listener != null) {
                        listener.getLogger().println("Skipping installation of " + archive + " to " + remote + ": " + x);
                    }
                    return false;
                } else {
                    throw x;
K
kohsuke 已提交
738 739
                }
            }
740 741 742 743 744 745

            if (con instanceof HttpURLConnection
                    && ((HttpURLConnection)con).getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
                return false;
            }

746
            long sourceTimestamp = con.getLastModified();
K
kohsuke 已提交
747

748 749 750 751 752 753 754
            if(this.exists()) {
                if(timestamp.exists() && sourceTimestamp ==timestamp.lastModified())
                    return false;   // already up to date
                this.deleteContents();
            } else {
                this.mkdirs();
            }
K
kohsuke 已提交
755

756 757 758
            if(listener!=null)
                listener.getLogger().println(message);

759 760 761 762 763 764 765 766 767 768 769 770 771
            if (isRemote()) {
                // First try to download from the slave machine.
                try {
                    act(new Unpack(archive));
                    timestamp.touch(sourceTimestamp);
                    return true;
                } catch (IOException x) {
                    if (listener != null) {
                        x.printStackTrace(listener.error("Failed to download " + archive + " from slave; will retry from master"));
                    }
                }
            }

772
            // for HTTP downloads, enable automatic retry for added resilience
773
            InputStream in = archive.getProtocol().startsWith("http") ? ProxyConfiguration.getInputStream(archive) : con.getInputStream();
774 775 776 777
            CountingInputStream cis = new CountingInputStream(in);
            try {
                if(archive.toExternalForm().endsWith(".zip"))
                    unzipFrom(cis);
778 779
                else
                    untarFrom(cis,GZIP);
780 781 782 783 784 785
            } catch (IOException e) {
                throw new IOException2(String.format("Failed to unpack %s (%d bytes read of total %d)",
                        archive,cis.getByteCount(),con.getContentLength()),e);
            }
            timestamp.touch(sourceTimestamp);
            return true;
K
kohsuke 已提交
786
        } catch (IOException e) {
787
            throw new IOException2("Failed to install "+archive+" to "+remote,e);
K
kohsuke 已提交
788
        }
K
kohsuke 已提交
789 790
    }

791 792
    // this reads from arbitrary URL
    private final class Unpack extends MasterToSlaveFileCallable<Void> {
793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
        private final URL archive;
        Unpack(URL archive) {
            this.archive = archive;
        }
        @Override public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
            InputStream in = archive.openStream();
            try {
                CountingInputStream cis = new CountingInputStream(in);
                try {
                    if (archive.toExternalForm().endsWith(".zip")) {
                        unzip(dir, cis);
                    } else {
                        readFromTar("input stream", dir, GZIP.extract(cis));
                    }
                } catch (IOException x) {
                    throw new IOException2(String.format("Failed to unpack %s (%d bytes read)", archive, cis.getByteCount()), x);
                }
            } finally {
                in.close();
            }
            return null;
        }
    }

K
kohsuke 已提交
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844
    /**
     * Reads the URL on the current VM, and writes all the data to this {@link FilePath}
     * (this is different from resolving URL remotely.)
     *
     * @since 1.293
     */
    public void copyFrom(URL url) throws IOException, InterruptedException {
        InputStream in = url.openStream();
        try {
            copyFrom(in);
        } finally {
            in.close();
        }
    }

    /**
     * Replaces the content of this file by the data from the given {@link InputStream}.
     *
     * @since 1.293
     */
    public void copyFrom(InputStream in) throws IOException, InterruptedException {
        OutputStream os = write();
        try {
            IOUtils.copy(in, os);
        } finally {
            os.close();
        }
    }
K
kohsuke 已提交
845 846

    /**
C
Christoph Kutzinski 已提交
847
     * Convenience method to call {@link FilePath#copyTo(FilePath)}.
K
kohsuke 已提交
848 849 850 851 852 853
     * 
     * @since 1.311
     */
    public void copyFrom(FilePath src) throws IOException, InterruptedException {
        src.copyTo(this);
    }
K
kohsuke 已提交
854

855 856 857 858 859 860
    /**
     * Place the data from {@link FileItem} into the file location specified by this {@link FilePath} object.
     */
    public void copyFrom(FileItem file) throws IOException, InterruptedException {
        if(channel==null) {
            try {
861
                file.write(writing(new File(remote)));
862 863 864 865 866 867 868 869 870 871 872
            } catch (IOException e) {
                throw e;
            } catch (Exception e) {
                throw new IOException2(e);
            }
        } else {
            InputStream i = file.getInputStream();
            OutputStream o = write();
            try {
                IOUtils.copy(i,o);
            } finally {
873 874 875 876 877
                try {
                    o.close();
                } finally {
                    i.close();
                }
878 879 880 881
            }
        }
    }

K
kohsuke 已提交
882 883 884
    /**
     * Code that gets executed on the machine where the {@link FilePath} is local.
     * Used to act on {@link FilePath}.
885
     * <strong>Warning:</code> implementations must be serializable, so prefer a static nested class to an inner class.
886 887 888 889 890
     *
     * <p>
     * Subtypes would likely want to extend from either {@link MasterToSlaveCallable}
     * or {@link SlaveToMasterFileCallable}.
     *
K
kohsuke 已提交
891 892
     * @see FilePath#act(FileCallable)
     */
893
    public interface FileCallable<T> extends Serializable, RoleSensitive {
K
kohsuke 已提交
894 895 896
        /**
         * Performs the computational task on the node where the data is located.
         *
897 898 899
         * <p>
         * All the exceptions are forwarded to the caller.
         *
K
kohsuke 已提交
900 901 902 903 904 905
         * @param f
         *      {@link File} that represents the local file that {@link FilePath} has represented.
         * @param channel
         *      The "back pointer" of the {@link Channel} that represents the communication
         *      with the node from where the code was sent.
         */
906
        T invoke(File f, VirtualChannel channel) throws IOException, InterruptedException;
K
kohsuke 已提交
907 908
    }

909 910 911 912 913 914 915 916 917
    /**
     * {@link FileCallable}s that can be executed anywhere, including the master.
     *
     * The code is the same as {@link SlaveToMasterFileCallable}, but used as a marker to
     * designate those impls that use {@link FilePathFilter}.
     */
    /*package*/ static abstract class SecureFileCallable<T> extends SlaveToMasterFileCallable<T> {
    }

K
kohsuke 已提交
918 919 920 921 922
    /**
     * Executes some program on the machine that this {@link FilePath} exists,
     * so that one can perform local file operations.
     */
    public <T> T act(final FileCallable<T> callable) throws IOException, InterruptedException {
923 924 925 926
        return act(callable,callable.getClass().getClassLoader());
    }

    private <T> T act(final FileCallable<T> callable, ClassLoader cl) throws IOException, InterruptedException {
K
kohsuke 已提交
927 928 929
        if(channel!=null) {
            // run this on a remote system
            try {
930
                DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<T>(callable, cl);
931 932 933 934 935 936
                Jenkins instance = Jenkins.getInstance();
                if (instance != null) { // this happens during unit tests
                    ExtensionList<FileCallableWrapperFactory> factories = instance.getExtensionList(FileCallableWrapperFactory.class);
                    for (FileCallableWrapperFactory factory : factories) {
                        wrapper = factory.wrap(wrapper);
                    }
937 938 939
                }

                return channel.call(wrapper);
940
            } catch (TunneledInterruptedException e) {
941
                throw (InterruptedException)new InterruptedException(e.getMessage()).initCause(e);
K
kohsuke 已提交
942 943
            } catch (AbortException e) {
                throw e;    // pass through so that the caller can catch it as AbortException
K
kohsuke 已提交
944 945
            } catch (IOException e) {
                // wrap it into a new IOException so that we get the caller's stack trace as well.
K
kohsuke 已提交
946
                throw new IOException2("remote file operation failed: "+remote+" at "+channel,e);
K
kohsuke 已提交
947 948 949
            }
        } else {
            // the file is on the local machine.
950
            return callable.invoke(new File(remote), Jenkins.MasterComputer.localChannel);
K
kohsuke 已提交
951 952 953
        }
    }

954 955
    /**
     * This extension point allows to contribute a wrapper around a fileCallable so that a plugin can "intercept" a
956 957 958
     * call.
     * <p>The {@link #wrap(hudson.remoting.DelegatingCallable)} method itself will be executed on master
     * (and may collect contextual data if needed) and the returned wrapper will be executed on remote.
959 960 961
     *
     * @since 1.482
     * @see AbstractInterceptorCallableWrapper
962
     */
963
    public static abstract class FileCallableWrapperFactory implements ExtensionPoint {
964 965 966 967 968

        public abstract <T> DelegatingCallable<T,IOException> wrap(DelegatingCallable<T,IOException> callable);

    }

969 970 971
    /**
     * Abstract {@link DelegatingCallable} that exposes an Before/After pattern for
     * {@link hudson.FilePath.FileCallableWrapperFactory} that want to implement AOP-style interceptors
972
     * @since 1.482
973
     */
974
    public static abstract class AbstractInterceptorCallableWrapper<T> implements DelegatingCallable<T, IOException> {
975
        private static final long serialVersionUID = 1L;
976

977
        private final DelegatingCallable<T, IOException> callable;
978 979 980 981 982

        public AbstractInterceptorCallableWrapper(DelegatingCallable<T, IOException> callable) {
            this.callable = callable;
        }

983 984
        @Override
        public final ClassLoader getClassLoader() {
985 986 987
            return callable.getClassLoader();
        }

988
        public final T call() throws IOException {
989 990 991 992 993 994 995 996
            before();
            try {
                return callable.call();
            } finally {
                after();
            }
        }

997 998 999
        /**
         * Executed before the actual FileCallable is invoked. This code will run on remote
         */
1000
        protected void before() {}
1001 1002

        /**
1003
         * Executed after the actual FileCallable is invoked (even if this one failed). This code will run on remote
1004
         */
1005 1006 1007 1008
        protected void after() {}
    }


1009 1010 1011 1012 1013 1014
    /**
     * Executes some program on the machine that this {@link FilePath} exists,
     * so that one can perform local file operations.
     */
    public <T> Future<T> actAsync(final FileCallable<T> callable) throws IOException, InterruptedException {
        try {
W
wolfgarnet 已提交
1015 1016 1017 1018 1019 1020 1021 1022 1023
            DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<T>(callable);
            Jenkins instance = Jenkins.getInstance();
            if (instance != null) { // this happens during unit tests
                ExtensionList<FileCallableWrapperFactory> factories = instance.getExtensionList(FileCallableWrapperFactory.class);
                for (FileCallableWrapperFactory factory : factories) {
                    wrapper = factory.wrap(wrapper);
                }
            }

1024
            return (channel!=null ? channel : Jenkins.MasterComputer.localChannel)
W
wolfgarnet 已提交
1025
                .callAsync(wrapper);
1026 1027 1028 1029 1030 1031
        } catch (IOException e) {
            // wrap it into a new IOException so that we get the caller's stack trace as well.
            throw new IOException2("remote file operation failed",e);
        }
    }

K
kohsuke 已提交
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
    /**
     * Executes some program on the machine that this {@link FilePath} exists,
     * so that one can perform local file operations.
     */
    public <V,E extends Throwable> V act(Callable<V,E> callable) throws IOException, InterruptedException, E {
        if(channel!=null) {
            // run this on a remote system
            return channel.call(callable);
        } else {
            // the file is on the local machine
            return callable.call();
        }
    }

K
Kohsuke Kawaguchi 已提交
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062
    /**
     * Takes a {@link FilePath}+{@link FileCallable} pair and returns the equivalent {@link Callable}.
     * When executing the resulting {@link Callable}, it executes {@link FileCallable#act(FileCallable)}
     * on this {@link FilePath}.
     *
     * @since 1.522
     */
    public <V> Callable<V,IOException> asCallableWith(final FileCallable<V> task) {
        return new Callable<V,IOException>() {
            @Override
            public V call() throws IOException {
                try {
                    return act(task);
                } catch (InterruptedException e) {
                    throw (IOException)new InterruptedIOException().initCause(e);
                }
            }
1063 1064

            @Override
1065 1066
            public void checkRoles(RoleChecker checker) throws SecurityException {
                task.checkRoles(checker);
1067 1068
            }

K
Kohsuke Kawaguchi 已提交
1069 1070 1071 1072
            private static final long serialVersionUID = 1L;
        };
    }

K
kohsuke 已提交
1073 1074 1075 1076 1077
    /**
     * Converts this file to the URI, relative to the machine
     * on which this file is available.
     */
    public URI toURI() throws IOException, InterruptedException {
1078
        return act(new SecureFileCallable<URI>() {
1079
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1080 1081 1082 1083 1084 1085
            public URI invoke(File f, VirtualChannel channel) {
                return f.toURI();
            }
        });
    }

1086 1087
    /**
     * Gets the {@link VirtualFile} representation of this {@link FilePath}
K
Kohsuke Kawaguchi 已提交
1088 1089
     *
     * @since 1.532
1090 1091 1092 1093 1094
     */
    public VirtualFile toVirtualFile() {
        return VirtualFile.forFilePath(this);
    }

K
kohsuke 已提交
1095 1096 1097
    /**
     * Creates this directory.
     */
K
kohsuke 已提交
1098
    public void mkdirs() throws IOException, InterruptedException {
1099
        if(!act(new SecureFileCallable<Boolean>() {
1100
            private static final long serialVersionUID = 1L;
1101
            public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
1102
                if(mkdirs(f) || f.exists())
1103 1104 1105
                    return true;    // OK

                // following Ant <mkdir> task to avoid possible race condition.
1106
                Thread.sleep(10);
1107 1108

                return f.mkdirs() || f.exists();
K
kohsuke 已提交
1109 1110 1111 1112 1113 1114 1115 1116 1117
            }
        }))
            throw new IOException("Failed to mkdirs: "+remote);
    }

    /**
     * Deletes this directory, including all its contents recursively.
     */
    public void deleteRecursive() throws IOException, InterruptedException {
1118
        act(new SecureFileCallable<Void>() {
1119
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1120
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1121
                deleteRecursive(deleting(f));
K
kohsuke 已提交
1122 1123 1124
                return null;
            }
        });
K
kohsuke 已提交
1125 1126 1127 1128 1129
    }

    /**
     * Deletes all the contents of this directory, but not the directory itself
     */
K
kohsuke 已提交
1130
    public void deleteContents() throws IOException, InterruptedException {
1131
        act(new SecureFileCallable<Void>() {
1132
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1133
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1134
                deleteContentsRecursive(f);
K
kohsuke 已提交
1135 1136 1137
                return null;
            }
        });
K
kohsuke 已提交
1138 1139
    }

1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
    private void deleteRecursive(File dir) throws IOException {
        if(!isSymlink(dir))
            deleteContentsRecursive(dir);
        try {
            deleteFile(deleting(dir));
        } catch (IOException e) {
            // if some of the child directories are big, it might take long enough to delete that
            // it allows others to create new files, causing problemsl ike JENKINS-10113
            // so give it one more attempt before we give up.
            if(!isSymlink(dir))
                deleteContentsRecursive(dir);
            deleteFile(deleting(dir));
        }
    }

    private void deleteContentsRecursive(File file) throws IOException {
        File[] files = file.listFiles();
        if(files==null)
            return;     // the directory didn't exist in the first place
        for (File child : files)
            deleteRecursive(child);
    }

K
kohsuke 已提交
1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173
    /**
     * Gets the file name portion except the extension.
     *
     * For example, "foo" for "foo.txt" and "foo.tar" for "foo.tar.gz".
     */
    public String getBaseName() {
        String n = getName();
        int idx = n.lastIndexOf('.');
        if (idx<0)  return n;
        return n.substring(0,idx);
    }
K
kohsuke 已提交
1174
    /**
K
Kohsuke Kawaguchi 已提交
1175
     * Gets just the file name portion without directories.
K
kohsuke 已提交
1176
     *
K
Kohsuke Kawaguchi 已提交
1177
     * For example, "foo.txt" for "../abc/foo.txt"
K
kohsuke 已提交
1178 1179
     */
    public String getName() {
1180 1181 1182 1183 1184
        String r = remote;
        if(r.endsWith("\\") || r.endsWith("/"))
            r = r.substring(0,r.length()-1);

        int len = r.length()-1;
K
kohsuke 已提交
1185
        while(len>=0) {
1186
            char ch = r.charAt(len);
K
kohsuke 已提交
1187 1188 1189 1190 1191
            if(ch=='\\' || ch=='/')
                break;
            len--;
        }

1192
        return r.substring(len+1);
K
kohsuke 已提交
1193 1194
    }

K
kohsuke 已提交
1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208
    /**
     * Short for {@code getParent().child(rel)}. Useful for getting other files in the same directory. 
     */
    public FilePath sibling(String rel) {
        return getParent().child(rel);
    }

    /**
     * Returns a {@link FilePath} by adding the given suffix to this path name.
     */
    public FilePath withSuffix(String suffix) {
        return new FilePath(channel,remote+suffix);
    }

K
kohsuke 已提交
1209
    /**
K
kohsuke 已提交
1210
     * The same as {@link FilePath#FilePath(FilePath,String)} but more OO.
1211
     * @param relOrAbsolute a relative or absolute path
K
kohsuke 已提交
1212
     * @return a file on the same channel
K
kohsuke 已提交
1213
     */
1214 1215
    public FilePath child(String relOrAbsolute) {
        return new FilePath(this,relOrAbsolute);
K
kohsuke 已提交
1216 1217 1218 1219
    }

    /**
     * Gets the parent file.
1220
     * @return parent FilePath or null if there is no parent
K
kohsuke 已提交
1221 1222
     */
    public FilePath getParent() {
1223 1224 1225
        int i = remote.length() - 2;
        for (; i >= 0; i--) {
            char ch = remote.charAt(i);
K
kohsuke 已提交
1226 1227 1228 1229
            if(ch=='\\' || ch=='/')
                break;
        }

1230
        return i >= 0 ? new FilePath( channel, remote.substring(0,i+1) ) : null;
K
kohsuke 已提交
1231 1232 1233
    }

    /**
K
kohsuke 已提交
1234
     * Creates a temporary file in the directory that this {@link FilePath} object designates.
J
Jørgen P. Tjernø 已提交
1235 1236 1237 1238 1239 1240 1241 1242 1243 1244
     *
     * @param prefix
     *      The prefix string to be used in generating the file's name; must be
     *      at least three characters long
     * @param suffix
     *      The suffix string to be used in generating the file's name; may be
     *      null, in which case the suffix ".tmp" will be used
     * @return
     *      The new FilePath pointing to the temporary file
     * @see File#createTempFile(String, String)
K
kohsuke 已提交
1245
     */
K
kohsuke 已提交
1246 1247
    public FilePath createTempFile(final String prefix, final String suffix) throws IOException, InterruptedException {
        try {
1248
            return new FilePath(this,act(new SecureFileCallable<String>() {
1249
                private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1250
                public String invoke(File dir, VirtualChannel channel) throws IOException {
1251
                    File f = writing(File.createTempFile(prefix, suffix, dir));
K
kohsuke 已提交
1252 1253 1254 1255 1256 1257 1258 1259 1260
                    return f.getName();
                }
            }));
        } catch (IOException e) {
            throw new IOException2("Failed to create a temp file on "+remote,e);
        }
    }

    /**
J
Jørgen P. Tjernø 已提交
1261
     * Creates a temporary file in this directory and set the contents to the
K
kohsuke 已提交
1262
     * given text (encoded in the platform default encoding)
J
Jørgen P. Tjernø 已提交
1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274
     *
     * @param prefix
     *      The prefix string to be used in generating the file's name; must be
     *      at least three characters long
     * @param suffix
     *      The suffix string to be used in generating the file's name; may be
     *      null, in which case the suffix ".tmp" will be used
     * @param contents
     *      The initial contents of the temporary file.
     * @return
     *      The new FilePath pointing to the temporary file
     * @see File#createTempFile(String, String)
K
kohsuke 已提交
1275 1276
     */
    public FilePath createTextTempFile(final String prefix, final String suffix, final String contents) throws IOException, InterruptedException {
1277 1278 1279 1280
        return createTextTempFile(prefix,suffix,contents,true);
    }

    /**
J
Jørgen P. Tjernø 已提交
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300
     * Creates a temporary file in this directory (or the system temporary
     * directory) and set the contents to the given text (encoded in the
     * platform default encoding)
     *
     * @param prefix
     *      The prefix string to be used in generating the file's name; must be
     *      at least three characters long
     * @param suffix
     *      The suffix string to be used in generating the file's name; may be
     *      null, in which case the suffix ".tmp" will be used
     * @param contents
     *      The initial contents of the temporary file.
     * @param inThisDirectory
     *      If true, then create this temporary in the directory pointed to by
     *      this.
     *      If false, then the temporary file is created in the system temporary
     *      directory (java.io.tmpdir)
     * @return
     *      The new FilePath pointing to the temporary file
     * @see File#createTempFile(String, String)
1301 1302
     */
    public FilePath createTextTempFile(final String prefix, final String suffix, final String contents, final boolean inThisDirectory) throws IOException, InterruptedException {
K
kohsuke 已提交
1303
        try {
1304
            return new FilePath(channel,act(new SecureFileCallable<String>() {
1305
                private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1306
                public String invoke(File dir, VirtualChannel channel) throws IOException {
1307
                    if(!inThisDirectory)
K
kohsuke 已提交
1308
                        dir = new File(System.getProperty("java.io.tmpdir"));
1309
                    else
1310
                        mkdirs(dir);
1311 1312 1313

                    File f;
                    try {
1314
                        f = creating(File.createTempFile(prefix, suffix, dir));
1315 1316 1317
                    } catch (IOException e) {
                        throw new IOException2("Failed to create a temporary directory in "+dir,e);
                    }
K
kohsuke 已提交
1318

1319
                    Writer w = new FileWriter(writing(f));
S
ssogabe 已提交
1320 1321 1322 1323 1324
                    try {
                        w.write(contents);
                    } finally {
                        w.close();
                    }
K
kohsuke 已提交
1325

K
kohsuke 已提交
1326
                    return f.getAbsolutePath();
K
kohsuke 已提交
1327 1328
                }
            }));
K
kohsuke 已提交
1329
        } catch (IOException e) {
K
kohsuke 已提交
1330
            throw new IOException2("Failed to create a temp file on "+remote,e);
K
kohsuke 已提交
1331
        }
K
kohsuke 已提交
1332 1333
    }

K
kohsuke 已提交
1334 1335
    /**
     * Creates a temporary directory inside the directory represented by 'this'
J
Jørgen P. Tjernø 已提交
1336 1337 1338 1339 1340 1341 1342 1343 1344
     *
     * @param prefix
     *      The prefix string to be used in generating the directory's name;
     *      must be at least three characters long
     * @param suffix
     *      The suffix string to be used in generating the directory's name; may
     *      be null, in which case the suffix ".tmp" will be used
     * @return
     *      The new FilePath pointing to the temporary directory
K
kohsuke 已提交
1345
     * @since 1.311
J
Jørgen P. Tjernø 已提交
1346
     * @see File#createTempFile(String, String)
K
kohsuke 已提交
1347 1348 1349
     */
    public FilePath createTempDir(final String prefix, final String suffix) throws IOException, InterruptedException {
        try {
1350
            return new FilePath(this,act(new SecureFileCallable<String>() {
1351
                private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363
                public String invoke(File dir, VirtualChannel channel) throws IOException {
                    File f = File.createTempFile(prefix, suffix, dir);
                    f.delete();
                    f.mkdir();
                    return f.getName();
                }
            }));
        } catch (IOException e) {
            throw new IOException2("Failed to create a temp directory on "+remote,e);
        }
    }

K
kohsuke 已提交
1364 1365
    /**
     * Deletes this file.
1366 1367
     * @throws IOException if it exists but could not be successfully deleted
     * @return true, for a modicum of compatibility
K
kohsuke 已提交
1368
     */
K
kohsuke 已提交
1369
    public boolean delete() throws IOException, InterruptedException {
1370
        act(new SecureFileCallable<Void>() {
1371
            private static final long serialVersionUID = 1L;
1372
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1373
                Util.deleteFile(deleting(f));
1374
                return null;
K
kohsuke 已提交
1375 1376
            }
        });
1377
        return true;
K
kohsuke 已提交
1378 1379 1380 1381 1382 1383
    }

    /**
     * Checks if the file exists.
     */
    public boolean exists() throws IOException, InterruptedException {
1384
        return act(new SecureFileCallable<Boolean>() {
1385
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1386
            public Boolean invoke(File f, VirtualChannel channel) throws IOException {
1387
                return stating(f).exists();
K
kohsuke 已提交
1388 1389
            }
        });
K
kohsuke 已提交
1390 1391
    }

K
kohsuke 已提交
1392 1393 1394 1395 1396
    /**
     * Gets the last modified time stamp of this file, by using the clock
     * of the machine where this file actually resides.
     *
     * @see File#lastModified()
K
kohsuke 已提交
1397
     * @see #touch(long)
K
kohsuke 已提交
1398 1399
     */
    public long lastModified() throws IOException, InterruptedException {
1400
        return act(new SecureFileCallable<Long>() {
1401
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1402
            public Long invoke(File f, VirtualChannel channel) throws IOException {
1403
                return stating(f).lastModified();
K
kohsuke 已提交
1404 1405
            }
        });
K
kohsuke 已提交
1406 1407
    }

K
kohsuke 已提交
1408 1409 1410 1411 1412 1413
    /**
     * Creates a file (if not already exist) and sets the timestamp.
     *
     * @since 1.299
     */
    public void touch(final long timestamp) throws IOException, InterruptedException {
1414
        act(new SecureFileCallable<Void>() {
C
Christoph Kutzinski 已提交
1415
            private static final long serialVersionUID = -5094638816500738429L;
K
kohsuke 已提交
1416 1417
            public Void invoke(File f, VirtualChannel channel) throws IOException {
                if(!f.exists())
1418 1419
                    new FileOutputStream(creating(f)).close();
                if(!stating(f).setLastModified(timestamp))
K
kohsuke 已提交
1420 1421 1422 1423 1424
                    throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp);
                return null;
            }
        });
    }
1425 1426
    
    private void setLastModifiedIfPossible(final long timestamp) throws IOException, InterruptedException {
1427
        String message = act(new SecureFileCallable<String>() {
1428 1429
            private static final long serialVersionUID = -828220335793641630L;
            public String invoke(File f, VirtualChannel channel) throws IOException {
1430
                if(!writing(f).setLastModified(timestamp)) {
1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446
                    if (Functions.isWindows()) {
                        // On Windows this seems to fail often. See JENKINS-11073
                        // Therefore don't fail, but just log a warning
                        return "Failed to set the timestamp of "+f+" to "+timestamp;
                    } else {
                        throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp);
                    }
                }
                return null;
            }
        });

        if (message!=null) {
            LOGGER.warning(message);
        }
    }
K
kohsuke 已提交
1447

K
kohsuke 已提交
1448 1449 1450 1451
    /**
     * Checks if the file is a directory.
     */
    public boolean isDirectory() throws IOException, InterruptedException {
1452
        return act(new SecureFileCallable<Boolean>() {
1453
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1454
            public Boolean invoke(File f, VirtualChannel channel) throws IOException {
1455
                return stating(f).isDirectory();
K
kohsuke 已提交
1456 1457
            }
        });
K
kohsuke 已提交
1458
    }
K
kohsuke 已提交
1459 1460 1461
    
    /**
     * Returns the file size in bytes.
K
kohsuke 已提交
1462 1463
     *
     * @since 1.129
K
kohsuke 已提交
1464 1465
     */
    public long length() throws IOException, InterruptedException {
1466
        return act(new SecureFileCallable<Long>() {
1467
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1468
            public Long invoke(File f, VirtualChannel channel) throws IOException {
1469
                return stating(f).length();
K
kohsuke 已提交
1470 1471 1472
            }
        });
    }
K
kohsuke 已提交
1473

K
kohsuke 已提交
1474 1475 1476 1477 1478
    /**
     * Sets the file permission.
     *
     * On Windows, no-op.
     *
K
kohsuke 已提交
1479 1480 1481
     * @param mask
     *      File permission mask. To simplify the permission copying,
     *      if the parameter is -1, this method becomes no-op.
1482 1483 1484 1485
     *      <p>
     *      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>,
     *      so preceded by a '0' in java notation, ie <code>chmod(0644)</code>
     *
K
kohsuke 已提交
1486
     * @since 1.303
K
kohsuke 已提交
1487
     * @see #mode()
K
kohsuke 已提交
1488 1489
     */
    public void chmod(final int mask) throws IOException, InterruptedException {
K
kohsuke 已提交
1490
        if(!isUnix() || mask==-1)   return;
1491
        act(new SecureFileCallable<Void>() {
1492
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1493
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1494
                _chmod(writing(f), mask);
R
rseguy 已提交
1495

K
kohsuke 已提交
1496 1497 1498 1499 1500
                return null;
            }
        });
    }

1501 1502 1503 1504 1505 1506 1507 1508 1509 1510
    /**
     * Run chmod via libc if we can, otherwise fall back to Ant.
     */
    private static void _chmod(File f, int mask) throws IOException {
        if (Functions.isWindows())  return; // noop

        try {
            if(LIBC.chmod(f.getAbsolutePath(),mask)!=0) {
                throw new IOException("Failed to chmod "+f+" : "+LIBC.strerror(Native.getLastError()));
            }
1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521
        } catch(NoClassDefFoundError e) {  // cf. https://groups.google.com/group/hudson-dev/browse_thread/thread/6d16c3e8ea0dbc9?hl=fr
            _chmodAnt(f, mask);
        } catch(UnsatisfiedLinkError e2) { // HUDSON-8155: use Ant's chmod task on non-GNU C systems
            _chmodAnt(f, mask);
        }
    }

    private static void _chmodAnt(File f, int mask) {
        if (!CHMOD_WARNED) { // only warn this once to avoid flooding the log
            CHMOD_WARNED = true;
            LOGGER.warning("GNU C Library not available: Using Ant's chmod task instead.");
1522
        }
1523 1524 1525 1526 1527
        Chmod chmodTask = new Chmod();
        chmodTask.setProject(new Project());
        chmodTask.setFile(f);
        chmodTask.setPerm(Integer.toOctalString(mask));
        chmodTask.execute();
1528 1529 1530 1531
    }

    private static boolean CHMOD_WARNED = false;

K
kohsuke 已提交
1532 1533 1534 1535 1536 1537 1538 1539
    /**
     * Gets the file permission bit mask.
     *
     * @return
     *      -1 on Windows, since such a concept doesn't make sense.
     * @since 1.311
     * @see #chmod(int)
     */
1540
    public int mode() throws IOException, InterruptedException, PosixException {
K
kohsuke 已提交
1541
        if(!isUnix())   return -1;
1542
        return act(new SecureFileCallable<Integer>() {
1543
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1544
            public Integer invoke(File f, VirtualChannel channel) throws IOException {
1545
                return IOUtils.mode(stating(f));
K
kohsuke 已提交
1546 1547 1548 1549
            }
        });
    }

K
kohsuke 已提交
1550
    /**
K
kohsuke 已提交
1551 1552 1553 1554 1555 1556 1557 1558 1559
     * List up files and directories in this directory.
     *
     * <p>
     * This method returns direct children of the directory denoted by the 'this' object.
     */
    public List<FilePath> list() throws IOException, InterruptedException {
        return list((FileFilter)null);
    }

K
kohsuke 已提交
1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575
    /**
     * List up subdirectories.
     *
     * @return can be empty but never null. Doesn't contain "." and ".."
     */
    public List<FilePath> listDirectories() throws IOException, InterruptedException {
        return list(new DirectoryFilter());
    }

    private static final class DirectoryFilter implements FileFilter, Serializable {
        public boolean accept(File f) {
            return f.isDirectory();
        }
        private static final long serialVersionUID = 1L;
    }

K
kohsuke 已提交
1576 1577
    /**
     * List up files in this directory, just like {@link File#listFiles(FileFilter)}.
K
kohsuke 已提交
1578 1579 1580 1581 1582 1583
     *
     * @param filter
     *      The optional filter used to narrow down the result.
     *      If non-null, must be {@link Serializable}.
     *      If this {@link FilePath} represents a remote path,
     *      the filter object will be executed on the remote machine.
K
kohsuke 已提交
1584
     */
K
kohsuke 已提交
1585
    public List<FilePath> list(final FileFilter filter) throws IOException, InterruptedException {
1586 1587 1588
        if (filter != null && !(filter instanceof Serializable)) {
            throw new IllegalArgumentException("Non-serializable filter of " + filter.getClass());
        }
1589
        return act(new SecureFileCallable<List<FilePath>>() {
1590
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1591
            public List<FilePath> invoke(File f, VirtualChannel channel) throws IOException {
1592
                File[] children = reading(f).listFiles(filter);
K
kohsuke 已提交
1593 1594 1595 1596 1597 1598 1599 1600
                if(children ==null)     return null;

                ArrayList<FilePath> r = new ArrayList<FilePath>(children.length);
                for (File child : children)
                    r.add(new FilePath(child));

                return r;
            }
1601
        }, (filter!=null?filter:this).getClass().getClassLoader());
K
kohsuke 已提交
1602 1603
    }

K
kohsuke 已提交
1604 1605 1606 1607
    /**
     * List up files in this directory that matches the given Ant-style filter.
     *
     * @param includes
K
kohsuke 已提交
1608
     *      See {@link FileSet} for the syntax. String like "foo/*.zip" or "foo/*&#42;/*.xml"
K
kohsuke 已提交
1609 1610
     * @return
     *      can be empty but always non-null.
K
kohsuke 已提交
1611 1612
     */
    public FilePath[] list(final String includes) throws IOException, InterruptedException {
B
bap2000 已提交
1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623
        return list(includes, null);
    }

    /**
     * List up files in this directory that matches the given Ant-style filter.
     *
     * @param includes
     * @param excludes
     *      See {@link FileSet} for the syntax. String like "foo/*.zip" or "foo/*&#42;/*.xml"
     * @return
     *      can be empty but always non-null.
1624
     * @since 1.407
B
bap2000 已提交
1625 1626
     */
    public FilePath[] list(final String includes, final String excludes) throws IOException, InterruptedException {
1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641
        return list(includes, excludes, true);
    }

    /**
     * List up files in this directory that matches the given Ant-style filter.
     *
     * @param includes
     * @param excludes
     *      See {@link FileSet} for the syntax. String like "foo/*.zip" or "foo/*&#42;/*.xml"
     * @param defaultExcludes whether to use the ant default excludes
     * @return
     *      can be empty but always non-null.
     * @since 1.465
     */
    public FilePath[] list(final String includes, final String excludes, final boolean defaultExcludes) throws IOException, InterruptedException {
1642
        return act(new SecureFileCallable<FilePath[]>() {
1643
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1644
            public FilePath[] invoke(File f, VirtualChannel channel) throws IOException {
1645
                String[] files = glob(reading(f), includes, excludes, defaultExcludes);
K
kohsuke 已提交
1646 1647 1648 1649 1650 1651 1652 1653 1654 1655

                FilePath[] r = new FilePath[files.length];
                for( int i=0; i<r.length; i++ )
                    r[i] = new FilePath(new File(f,files[i]));

                return r;
            }
        });
    }

K
kohsuke 已提交
1656 1657
    /**
     * Runs Ant glob expansion.
K
kohsuke 已提交
1658 1659 1660
     *
     * @return
     *      A set of relative file names from the base directory.
K
kohsuke 已提交
1661
     */
1662
    private static String[] glob(File dir, String includes, String excludes, boolean defaultExcludes) throws IOException {
K
kohsuke 已提交
1663
        if(isAbsolute(includes))
W
wyukawa 已提交
1664
            throw new IOException("Expecting Ant GLOB pattern, but saw '"+includes+"'. See http://ant.apache.org/manual/Types/fileset.html for syntax");
B
bap2000 已提交
1665
        FileSet fs = Util.createFileSet(dir,includes,excludes);
1666
        fs.setDefaultexcludes(defaultExcludes);
K
kohsuke 已提交
1667 1668 1669 1670 1671
        DirectoryScanner ds = fs.getDirectoryScanner(new Project());
        String[] files = ds.getIncludedFiles();
        return files;
    }

K
kohsuke 已提交
1672 1673 1674
    /**
     * Reads this file.
     */
1675
    public InputStream read() throws IOException, InterruptedException {
K
kohsuke 已提交
1676
        if(channel==null)
1677
            return new FileInputStream(reading(new File(remote)));
K
kohsuke 已提交
1678 1679

        final Pipe p = Pipe.createRemoteToLocal();
1680
        actAsync(new SecureFileCallable<Void>() {
1681
            private static final long serialVersionUID = 1L;
1682 1683 1684 1685

            @Override
            public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
                FileInputStream fis = null;
1686
                try {
1687 1688
                    fis = new FileInputStream(reading(f));
                    Util.copyStream(fis, p.getOut());
1689 1690 1691 1692 1693
                    return null;
                } finally {
                    IOUtils.closeQuietly(fis);
                    IOUtils.closeQuietly(p.getOut());
                }
K
kohsuke 已提交
1694 1695 1696 1697 1698 1699
            }
        });

        return p.getIn();
    }

K
kohsuke 已提交
1700 1701 1702
    /**
     * Reads this file into a string, by using the current system encoding.
     */
1703
    public String readToString() throws IOException, InterruptedException {
K
kohsuke 已提交
1704 1705 1706 1707 1708 1709 1710 1711
        InputStream in = read();
        try {
            return IOUtils.toString(in);
        } finally {
            in.close();
        }
    }

K
kohsuke 已提交
1712 1713 1714
    /**
     * Writes to this file.
     * If this file already exists, it will be overwritten.
K
kohsuke 已提交
1715
     * If the directory doesn't exist, it will be created.
K
Kohsuke Kawaguchi 已提交
1716 1717 1718 1719 1720 1721 1722 1723 1724 1725
     *
     * <P>
     * I/O operation to remote {@link FilePath} happens asynchronously, meaning write operations to the returned
     * {@link OutputStream} will return without receiving a confirmation from the remote that the write happened.
     * I/O operations also happens asynchronously from the {@link Channel#call(Callable)} operations, so if
     * you write to a remote file and then execute {@link Channel#call(Callable)} and try to access the newly copied
     * file, it might not be fully written yet.
     *
     * <p>
     *
K
kohsuke 已提交
1726
     */
1727
    public OutputStream write() throws IOException, InterruptedException {
1728
        if(channel==null) {
1729
            File f = new File(remote).getAbsoluteFile();
1730 1731
            mkdirs(f.getParentFile());
            return new FileOutputStream(writing(f));
1732
        }
K
kohsuke 已提交
1733

1734
        return act(new SecureFileCallable<OutputStream>() {
1735
            private static final long serialVersionUID = 1L;
1736 1737
            public OutputStream invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
                f = f.getAbsoluteFile();
1738 1739
                mkdirs(f.getParentFile());
                FileOutputStream fos = new FileOutputStream(writing(f));
1740
                return new RemoteOutputStream(fos);
K
kohsuke 已提交
1741 1742 1743 1744
            }
        });
    }

K
kohsuke 已提交
1745 1746 1747 1748 1749 1750 1751 1752
    /**
     * Overwrites this file by placing the given String as the content.
     *
     * @param encoding
     *      Null to use the platform default encoding.
     * @since 1.105
     */
    public void write(final String content, final String encoding) throws IOException, InterruptedException {
1753
        act(new SecureFileCallable<Void>() {
1754
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1755
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1756 1757
                mkdirs(f.getParentFile());
                FileOutputStream fos = new FileOutputStream(writing(f));
K
kohsuke 已提交
1758
                Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos);
K
kohsuke 已提交
1759 1760 1761
                try {
                    w.write(content);
                } finally {
K
kohsuke 已提交
1762
                    w.close();
K
kohsuke 已提交
1763 1764 1765 1766 1767 1768
                }
                return null;
            }
        });
    }

K
kohsuke 已提交
1769 1770
    /**
     * Computes the MD5 digest of the file in hex string.
1771
     * @see Util#getDigestOf(File)
K
kohsuke 已提交
1772 1773
     */
    public String digest() throws IOException, InterruptedException {
1774
        return act(new SecureFileCallable<String>() {
1775
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1776
            public String invoke(File f, VirtualChannel channel) throws IOException {
1777
                return Util.getDigestOf(reading(f));
K
kohsuke 已提交
1778 1779 1780 1781
            }
        });
    }

1782 1783 1784 1785 1786 1787 1788 1789
    /**
     * Rename this file/directory to the target filepath.  This FilePath and the target must
     * be on the some host
     */
    public void renameTo(final FilePath target) throws IOException, InterruptedException {
    	if(this.channel != target.channel) {
    		throw new IOException("renameTo target must be on the same host");
    	}
1790
        act(new SecureFileCallable<Void>() {
1791
            private static final long serialVersionUID = 1L;
1792
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1793
            	reading(f).renameTo(creating(new File(target.remote)));
1794
                return null;
K
kohsuke 已提交
1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807
            }
        });
    }

    /**
     * Moves all the contents of this directory into the specified directory, then delete this directory itself.
     *
     * @since 1.308.
     */
    public void moveAllChildrenTo(final FilePath target) throws IOException, InterruptedException {
        if(this.channel != target.channel) {
            throw new IOException("pullUpTo target must be on the same host");
        }
1808
        act(new SecureFileCallable<Void>() {
1809
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1810 1811 1812
            public Void invoke(File f, VirtualChannel channel) throws IOException {
                File t = new File(target.getRemote());
                
1813
                for(File child : reading(f).listFiles()) {
K
kohsuke 已提交
1814
                    File target = new File(t, child.getName());
1815
                    if(!stating(child).renameTo(creating(target)))
K
kohsuke 已提交
1816 1817
                        throw new IOException("Failed to rename "+child+" to "+target);
                }
1818
                deleting(f).delete();
K
kohsuke 已提交
1819
                return null;
1820 1821 1822 1823
            }
        });
    }

K
kohsuke 已提交
1824 1825 1826 1827 1828
    /**
     * Copies this file to the specified target.
     */
    public void copyTo(FilePath target) throws IOException, InterruptedException {
        try {
K
kohsuke 已提交
1829 1830 1831 1832 1833 1834 1835 1836
            OutputStream out = target.write();
            try {
                copyTo(out);
            } finally {
                out.close();
            }
        } catch (IOException e) {
            throw new IOException2("Failed to copy "+this+" to "+target,e);
K
kohsuke 已提交
1837 1838 1839
        }
    }

K
kohsuke 已提交
1840
    /**
1841
     * Copies this file to the specified target, with file permissions and other meta attributes intact.
K
kohsuke 已提交
1842 1843 1844
     * @since 1.311
     */
    public void copyToWithPermission(FilePath target) throws IOException, InterruptedException {
K
kohsuke 已提交
1845
        copyTo(target);
K
kohsuke 已提交
1846 1847
        // copy file permission
        target.chmod(mode());
1848
        target.setLastModifiedIfPossible(lastModified());
K
kohsuke 已提交
1849 1850
    }

K
kohsuke 已提交
1851 1852 1853 1854 1855 1856
    /**
     * Sends the contents of this file into the given {@link OutputStream}.
     */
    public void copyTo(OutputStream os) throws IOException, InterruptedException {
        final OutputStream out = new RemoteOutputStream(os);

1857
        act(new SecureFileCallable<Void>() {
C
Christoph Kutzinski 已提交
1858
            private static final long serialVersionUID = 4088559042349254141L;
K
kohsuke 已提交
1859
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1860 1861
                FileInputStream fis = null;
                try {
1862
                    fis = new FileInputStream(reading(f));
1863 1864 1865 1866 1867 1868
                    Util.copyStream(fis,out);
                    return null;
                } finally {
                    IOUtils.closeQuietly(fis);
                    IOUtils.closeQuietly(out);
                }
K
kohsuke 已提交
1869 1870
            }
        });
1871

K
Kohsuke Kawaguchi 已提交
1872 1873
        // make sure the writes fully got delivered to 'os' before we return.
        // this is needed because I/O operation is asynchronous
1874 1875 1876
        syncIO();
    }

K
Kohsuke Kawaguchi 已提交
1877 1878 1879 1880 1881
    /**
     * With fix to JENKINS-11251 (remoting 2.15), this is no longer necessary.
     * But I'm keeping it for a while so that users who manually deploy slave.jar has time to deploy new version
     * before this goes away.
     */
1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901
    private void syncIO() throws InterruptedException {
        try {
            if (channel!=null)
                _syncIO();
        } catch (AbstractMethodError e) {
            // legacy slave.jar. Handle this gracefully
            try {
                LOGGER.log(Level.WARNING,"Looks like an old slave.jar. Please update "+ Which.jarFile(Channel.class)+" to the new version",e);
            } catch (IOException _) {
                // really ignore this time
            }
        }
    }

    /**
     * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067
     * on BugParade for more details.
     */
    private void _syncIO() throws InterruptedException {
        channel.syncLocalIO();
K
kohsuke 已提交
1902 1903
    }

K
kohsuke 已提交
1904 1905
    /**
     * Copies the contents of this directory recursively into the specified target directory.
C
Christoph Kutzinski 已提交
1906 1907 1908
     * 
     * @return
     *      the number of files copied.
K
kohsuke 已提交
1909 1910 1911 1912 1913 1914
     * @since 1.312 
     */
    public int copyRecursiveTo(FilePath target) throws IOException, InterruptedException {
        return copyRecursiveTo("**/*",target);
    }

C
Christoph Kutzinski 已提交
1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925
    /**
     * Copies the files that match the given file mask to the specified target node.
     *
     * @param fileMask
     *      Ant GLOB pattern.
     *      String like "foo/bar/*.xml" Multiple patterns can be separated
     *      by ',', and whitespace can surround ',' (so that you can write
     *      "abc, def" and "abc,def" to mean the same thing.
     * @return
     *      the number of files copied.
     */
K
kohsuke 已提交
1926 1927 1928 1929
    public int copyRecursiveTo(String fileMask, FilePath target) throws IOException, InterruptedException {
        return copyRecursiveTo(fileMask,null,target);
    }

K
kohsuke 已提交
1930 1931 1932
    /**
     * Copies the files that match the given file mask to the specified target node.
     *
K
kohsuke 已提交
1933 1934 1935 1936 1937
     * @param fileMask
     *      Ant GLOB pattern.
     *      String like "foo/bar/*.xml" Multiple patterns can be separated
     *      by ',', and whitespace can surround ',' (so that you can write
     *      "abc, def" and "abc,def" to mean the same thing.
K
kohsuke 已提交
1938 1939
     * @param excludes
     *      Files to be excluded. Can be null.
K
kohsuke 已提交
1940 1941 1942
     * @return
     *      the number of files copied.
     */
K
kohsuke 已提交
1943
    public int copyRecursiveTo(final String fileMask, final String excludes, final FilePath target) throws IOException, InterruptedException {
1944 1945 1946 1947 1948
        return copyRecursiveTo(new DirScanner.Glob(fileMask, excludes), target, fileMask);
    }

    /**
     * Copies files according to a specified scanner to a target node.
J
Jesse Glick 已提交
1949
     * @param scanner a way of enumerating some files (must be serializable for possible delivery to remote side)
1950 1951 1952
     * @param target the destination basedir
     * @param description a description of the fileset, for logging purposes
     * @return the number of files copied
K
Kohsuke Kawaguchi 已提交
1953
     * @since 1.532
1954 1955
     */
    public int copyRecursiveTo(final DirScanner scanner, final FilePath target, final String description) throws IOException, InterruptedException {
K
kohsuke 已提交
1956 1957
        if(this.channel==target.channel) {
            // local to local copy.
1958
            return act(new SecureFileCallable<Integer>() {
1959
                private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1960
                public Integer invoke(File base, VirtualChannel channel) throws IOException {
1961
                    if(!base.exists())  return 0;
K
kohsuke 已提交
1962
                    assert target.channel==null;
1963 1964
                    final File dest = new File(target.remote);
                    final AtomicInteger count = new AtomicInteger();
1965 1966 1967
                    scanner.scan(base, reading(new FileVisitor() {
                        @Override
                        public void visit(File f, String relativePath) throws IOException {
1968 1969
                            if (f.isFile()) {
                                File target = new File(dest, relativePath);
1970 1971
                                mkdirs(target.getParentFile());
                                Util.copyFile(f, writing(target));
1972
                                count.incrementAndGet();
K
kohsuke 已提交
1973
                            }
1974
                        }
1975 1976 1977

                        @Override
                        public boolean understandsSymlink() {
1978 1979
                            return true;
                        }
1980 1981 1982

                        @Override
                        public void visitSymlink(File link, String target, String relativePath) throws IOException {
1983
                            try {
1984
                                writing(new File(dest, target));
1985 1986
                                Util.createSymlink(dest, target, relativePath, TaskListener.NULL);
                            } catch (InterruptedException x) {
J
Jesse Glick 已提交
1987
                                throw (IOException) new IOException(x.toString()).initCause(x);
K
kohsuke 已提交
1988
                            }
1989
                            count.incrementAndGet();
K
kohsuke 已提交
1990
                        }
1991
                    }));
1992
                    return count.get();
K
kohsuke 已提交
1993 1994
                }
            });
1995 1996 1997 1998 1999
        } else
        if(this.channel==null) {
            // local -> remote copy
            final Pipe pipe = Pipe.createLocalToRemote();

2000
            Future<Void> future = target.actAsync(new SecureFileCallable<Void>() {
2001
                private static final long serialVersionUID = 1L;
2002
                public Void invoke(File f, VirtualChannel channel) throws IOException {
2003
                    try {
2004
                        readFromTar(remote + '/' + description, f,TarCompression.GZIP.extract(pipe.getIn()));
2005 2006 2007 2008
                        return null;
                    } finally {
                        pipe.getIn().close();
                    }
2009 2010
                }
            });
2011
            Future<Integer> future2 = actAsync(new SecureFileCallable<Integer>() {
2012 2013
                private static final long serialVersionUID = 1L;
                @Override public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
2014
                    return writeToTar(new File(remote), scanner, TarCompression.GZIP.compress(pipe.getOut()));
2015 2016
                }
            });
2017
            try {
2018
                // JENKINS-9540 in case the reading side failed, report that error first
2019
                future.get();
2020
                return future2.get();
2021 2022 2023
            } catch (ExecutionException e) {
                throw new IOException2(e);
            }
K
kohsuke 已提交
2024
        } else {
2025 2026
            // remote -> local copy
            final Pipe pipe = Pipe.createRemoteToLocal();
K
kohsuke 已提交
2027

2028
            Future<Integer> future = actAsync(new SecureFileCallable<Integer>() {
2029
                private static final long serialVersionUID = 1L;
2030
                public Integer invoke(File f, VirtualChannel channel) throws IOException {
2031
                    try {
2032
                        return writeToTar(f, scanner, TarCompression.GZIP.compress(pipe.getOut()));
2033 2034 2035
                    } finally {
                        pipe.getOut().close();
                    }
K
kohsuke 已提交
2036 2037
                }
            });
2038
            try {
2039
                readFromTar(remote + '/' + description,new File(target.remote),TarCompression.GZIP.extract(pipe.getIn()));
2040 2041 2042 2043 2044 2045
            } catch (IOException e) {// BuildException or IOException
                try {
                    future.get(3,TimeUnit.SECONDS);
                    throw e;    // the remote side completed successfully, so the error must be local
                } catch (ExecutionException x) {
                    // report both errors
2046
                    throw new IOException2(Functions.printThrowable(e),x);
2047
                } catch (TimeoutException _) {
2048 2049 2050 2051
                    // remote is hanging
                    throw e;
                }
            }
2052 2053 2054 2055 2056 2057 2058 2059
            try {
                return future.get();
            } catch (ExecutionException e) {
                throw new IOException2(e);
            }
        }
    }

2060 2061 2062 2063 2064 2065 2066 2067

    /**
     * Writes files in 'this' directory to a tar stream.
     *
     * @param glob
     *      Ant file pattern mask, like "**&#x2F;*.java".
     */
    public int tar(OutputStream out, final String glob) throws IOException, InterruptedException {
2068
        return archive(ArchiverFactory.TAR, out, glob);
2069 2070 2071
    }

    public int tar(OutputStream out, FileFilter filter) throws IOException, InterruptedException {
2072
        return archive(ArchiverFactory.TAR, out, filter);
2073 2074
    }

2075 2076 2077 2078
    /**
     * Uses the given scanner on 'this' directory to list up files and then archive it to a tar stream.
     */
    public int tar(OutputStream out, DirScanner scanner) throws IOException, InterruptedException {
2079
        return archive(ArchiverFactory.TAR, out, scanner);
2080 2081
    }

2082 2083 2084 2085 2086 2087
    /**
     * Writes to a tar stream and stores obtained files to the base dir.
     *
     * @return
     *      number of files/directories that are written.
     */
2088
    private Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException {
2089
        Archiver tw = ArchiverFactory.TAR.create(out);
2090
        try {
2091
            scanner.scan(baseDir,reading(tw));
2092 2093 2094
        } finally {
            tw.close();
        }
2095
        return tw.countEntries();
2096 2097 2098 2099 2100
    }

    /**
     * Reads from a tar stream and stores obtained files to the base dir.
     */
2101
    private void readFromTar(String name, File baseDir, InputStream in) throws IOException {
K
kohsuke 已提交
2102
        TarInputStream t = new TarInputStream(in);
2103
        try {
K
kohsuke 已提交
2104 2105 2106 2107
            TarEntry te;
            while ((te = t.getNextEntry()) != null) {
                File f = new File(baseDir,te.getName());
                if(te.isDirectory()) {
2108
                    mkdirs(f);
K
kohsuke 已提交
2109 2110
                } else {
                    File parent = f.getParentFile();
2111 2112
                    if (parent != null) mkdirs(parent);
                    writing(f);
K
kohsuke 已提交
2113

K
Kohsuke Kawaguchi 已提交
2114 2115 2116 2117 2118
                    byte linkFlag = (Byte) LINKFLAG_FIELD.get(te);
                    if (linkFlag==TarEntry.LF_SYMLINK) {
                        new FilePath(f).symlinkTo(te.getLinkName(), TaskListener.NULL);
                    } else {
                        IOUtils.copy(t,f);
2119 2120 2121 2122 2123

                        f.setLastModified(te.getModTime().getTime());
                        int mode = te.getMode()&0777;
                        if(mode!=0 && !Functions.isWindows()) // be defensive
                            _chmod(f,mode);
K
Kohsuke Kawaguchi 已提交
2124
                    }
K
kohsuke 已提交
2125 2126 2127 2128
                }
            }
        } catch(IOException e) {
            throw new IOException2("Failed to extract "+name,e);
K
Kohsuke Kawaguchi 已提交
2129 2130 2131 2132 2133
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // process this later
            throw new IOException2("Failed to extract "+name,e);
        } catch (IllegalAccessException e) {
            throw new IOException2("Failed to extract "+name,e);
K
kohsuke 已提交
2134 2135
        } finally {
            t.close();
2136
        }
K
kohsuke 已提交
2137 2138
    }

2139 2140
    /**
     * Creates a {@link Launcher} for starting processes on the node
K
typo.  
kohsuke 已提交
2141
     * that has this file.
2142
     * @since 1.89
2143
     */
2144
    public Launcher createLauncher(TaskListener listener) throws IOException, InterruptedException {
2145 2146 2147
        if(channel==null)
            return new LocalLauncher(listener);
        else
2148 2149 2150
            return new RemoteLauncher(listener,channel,channel.call(new IsUnix()));
    }

2151
    private static final class IsUnix extends MasterToSlaveCallable<Boolean,IOException> {
2152 2153 2154 2155
        public Boolean call() throws IOException {
            return File.pathSeparatorChar==':';
        }
        private static final long serialVersionUID = 1L;
2156 2157
    }

2158
    /**
K
kohsuke 已提交
2159
     * Validates the ant file mask (like "foo/bar/*.txt, zot/*.jar")
2160 2161
     * against this directory, and try to point out the problem.
     *
K
kohsuke 已提交
2162
     * <p>
K
kohsuke 已提交
2163
     * This is useful in conjunction with {@link FormValidation}.
K
kohsuke 已提交
2164
     *
2165
     * @return
2166
     *      null if no error was found. Otherwise returns a human readable error message.
2167
     * @since 1.90
K
kohsuke 已提交
2168
     * @see #validateFileMask(FilePath, String)
2169
     */
K
kohsuke 已提交
2170
    public String validateAntFileMask(final String fileMasks) throws IOException, InterruptedException {
2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187
        return validateAntFileMask(fileMasks, Integer.MAX_VALUE);
    }

    /**
     * Like {@link #validateAntFileMask(String)} but performing only a bounded number of operations.
     * <p>Whereas the unbounded overload is appropriate for calling from cancelable, long-running tasks such as build steps,
     * this overload should be used when an answer is needed quickly, such as for {@link #validateFileMask(String)}
     * or anything else returning {@link FormValidation}.
     * <p>If a positive match is found, {@code null} is returned immediately.
     * A message is returned in case the file pattern can definitely be determined to not match anything in the directory within the alloted time.
     * If the time runs out without finding a match but without ruling out the possibility that there might be one, {@link InterruptedException} is thrown,
     * 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).
     * @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
     * @throws InterruptedException not only in case of a channel failure, but also if too many operations were performed without finding any matches
     * @since 1.484
     */
    public String validateAntFileMask(final String fileMasks, final int bound) throws IOException, InterruptedException {
2188
        return act(new MasterToSlaveFileCallable<String>() {
2189 2190
            private static final long serialVersionUID = 1;
            public String invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
2191 2192 2193
                if(fileMasks.startsWith("~"))
                    return Messages.FilePath_TildaDoesntWork();

K
kohsuke 已提交
2194
                StringTokenizer tokens = new StringTokenizer(fileMasks,",");
K
kohsuke 已提交
2195 2196 2197

                while(tokens.hasMoreTokens()) {
                    final String fileMask = tokens.nextToken().trim();
2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223
                    if(hasMatch(dir,fileMask))
                        continue;   // no error on this portion

                    // in 1.172 we introduced an incompatible change to stop using ' ' as the separator
                    // so see if we can match by using ' ' as the separator
                    if(fileMask.contains(" ")) {
                        boolean matched = true;
                        for (String token : Util.tokenize(fileMask))
                            matched &= hasMatch(dir,token);
                        if(matched)
                            return Messages.FilePath_validateAntFileMask_whitespaceSeprator();
                    }

                    // a common mistake is to assume the wrong base dir, and there are two variations
                    // to this: (1) the user gave us aa/bb/cc/dd where cc/dd was correct
                    // and (2) the user gave us cc/dd where aa/bb/cc/dd was correct.

                    {// check the (1) above first
                        String f=fileMask;
                        while(true) {
                            int idx = findSeparator(f);
                            if(idx==-1)     break;
                            f=f.substring(idx+1);

                            if(hasMatch(dir,f))
                                return Messages.FilePath_validateAntFileMask_doesntMatchAndSuggest(fileMask,f);
K
kohsuke 已提交
2224
                        }
2225
                    }
2226

2227
                    {// check the (2) above next as this is more expensive.
2228
                        // Try prepending "**/" to see if that results in a match
2229
                        FileSet fs = Util.createFileSet(reading(dir),"**/"+fileMask);
2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252
                        DirectoryScanner ds = fs.getDirectoryScanner(new Project());
                        if(ds.getIncludedFilesCount()!=0) {
                            // try shorter name first so that the suggestion results in least amount of changes
                            String[] names = ds.getIncludedFiles();
                            Arrays.sort(names,SHORTER_STRING_FIRST);
                            for( String f : names) {
                                // now we want to decompose f to the leading portion that matched "**"
                                // and the trailing portion that matched the file mask, so that
                                // we can suggest the user error.
                                //
                                // this is not a very efficient/clever way to do it, but it's relatively simple

                                String prefix="";
                                while(true) {
                                    int idx = findSeparator(f);
                                    if(idx==-1)     break;

                                    prefix+=f.substring(0,idx)+'/';
                                    f=f.substring(idx+1);
                                    if(hasMatch(dir,prefix+fileMask))
                                        return Messages.FilePath_validateAntFileMask_doesntMatchAndSuggest(fileMask, prefix+fileMask);
                                }
                            }
K
kohsuke 已提交
2253
                        }
2254
                    }
2255

2256 2257 2258 2259 2260 2261 2262 2263
                    {// finally, see if we can identify any sub portion that's valid. Otherwise bail out
                        String previous = null;
                        String pattern = fileMask;

                        while(true) {
                            if(hasMatch(dir,pattern)) {
                                // found a match
                                if(previous==null)
S
sogabe 已提交
2264
                                    return Messages.FilePath_validateAntFileMask_portionMatchAndSuggest(fileMask,pattern);
2265
                                else
S
sogabe 已提交
2266
                                    return Messages.FilePath_validateAntFileMask_portionMatchButPreviousNotMatchAndSuggest(fileMask,pattern,previous);
2267 2268 2269 2270 2271
                            }

                            int idx = findSeparator(pattern);
                            if(idx<0) {// no more path component left to go back
                                if(pattern.equals(fileMask))
S
sogabe 已提交
2272
                                    return Messages.FilePath_validateAntFileMask_doesntMatchAnything(fileMask);
2273
                                else
S
sogabe 已提交
2274
                                    return Messages.FilePath_validateAntFileMask_doesntMatchAnythingAndSuggest(fileMask,pattern);
2275 2276 2277 2278 2279 2280
                            }

                            // cut off the trailing component and try again
                            previous = pattern;
                            pattern = pattern.substring(0,idx);
                        }
K
kohsuke 已提交
2281
                    }
2282
                }
K
kohsuke 已提交
2283 2284

                return null; // no error
2285
            }
2286

2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297
            private boolean hasMatch(File dir, String pattern) throws InterruptedException {
                class Cancel extends RuntimeException {}
                DirectoryScanner ds = bound == Integer.MAX_VALUE ? new DirectoryScanner() : new DirectoryScanner() {
                    int ticks;
                    @Override public synchronized boolean isCaseSensitive() {
                        if (!filesIncluded.isEmpty() || !dirsIncluded.isEmpty() || ticks++ > bound) {
                            throw new Cancel();
                        }
                        return super.isCaseSensitive();
                    }
                };
2298
                ds.setBasedir(reading(dir));
2299 2300 2301 2302 2303 2304 2305 2306 2307 2308
                ds.setIncludes(new String[] {pattern});
                try {
                    ds.scan();
                } catch (Cancel c) {
                    if (ds.getIncludedFilesCount()!=0 || ds.getIncludedDirsCount()!=0) {
                        return true;
                    } else {
                        throw new InterruptedException("no matches found within " + bound);
                    }
                }
2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321
                return ds.getIncludedFilesCount()!=0 || ds.getIncludedDirsCount()!=0;
            }

            /**
             * Finds the position of the first path separator.
             */
            private int findSeparator(String pattern) {
                int idx1 = pattern.indexOf('\\');
                int idx2 = pattern.indexOf('/');
                if(idx1==-1)    return idx2;
                if(idx2==-1)    return idx1;
                return Math.min(idx1,idx2);
            }
2322 2323 2324
        });
    }

K
kohsuke 已提交
2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340
    /**
     * Shortcut for {@link #validateFileMask(String)} in case the left-hand side can be null.
     */
    public static FormValidation validateFileMask(FilePath pathOrNull, String value) throws IOException {
        if(pathOrNull==null) return FormValidation.ok();
        return pathOrNull.validateFileMask(value);
    }

    /**
     * Short for {@code validateFileMask(value,true)} 
     */
    public FormValidation validateFileMask(String value) throws IOException {
        return validateFileMask(value,true);
    }

    /**
2341
     * Checks the GLOB-style file mask. See {@link #validateAntFileMask(String)}.
2342 2343
     * Requires configure permission on ancestor AbstractProject object in request,
     * or admin permission if no such ancestor is found.
K
kohsuke 已提交
2344 2345 2346
     * @since 1.294
     */
    public FormValidation validateFileMask(String value, boolean errorIfNotExist) throws IOException {
2347
        checkPermissionForValidate();
2348

K
kohsuke 已提交
2349 2350 2351 2352 2353 2354 2355 2356
        value = fixEmpty(value);
        if(value==null)
            return FormValidation.ok();

        try {
            if(!exists()) // no workspace. can't check
                return FormValidation.ok();

2357
            String msg = validateAntFileMask(value, 10000);
K
kohsuke 已提交
2358 2359 2360
            if(errorIfNotExist)     return FormValidation.error(msg);
            else                    return FormValidation.warning(msg);
        } catch (InterruptedException e) {
2361
            return FormValidation.ok(Messages.FilePath_did_not_manage_to_validate_may_be_too_sl(value));
K
kohsuke 已提交
2362 2363 2364 2365 2366
        }
    }

    /**
     * Validates a relative file path from this {@link FilePath}.
2367 2368
     * Requires configure permission on ancestor AbstractProject object in request,
     * or admin permission if no such ancestor is found.
K
kohsuke 已提交
2369 2370 2371 2372 2373 2374 2375 2376 2377 2378
     *
     * @param value
     *      The relative path being validated.
     * @param errorIfNotExist
     *      If true, report an error if the given relative path doesn't exist. Otherwise it's a warning.
     * @param expectingFile
     *      If true, we expect the relative path to point to a file.
     *      Otherwise, the relative path is expected to be pointing to a directory.
     */
    public FormValidation validateRelativePath(String value, boolean errorIfNotExist, boolean expectingFile) throws IOException {
2379
        checkPermissionForValidate();
K
kohsuke 已提交
2380 2381 2382 2383

        value = fixEmpty(value);

        // none entered yet, or something is seriously wrong
2384
        if(value==null) return FormValidation.ok();
K
kohsuke 已提交
2385 2386

        // a common mistake is to use wildcard
S
sogabe 已提交
2387
        if(value.contains("*")) return FormValidation.error(Messages.FilePath_validateRelativePath_wildcardNotAllowed());
K
kohsuke 已提交
2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398

        try {
            if(!exists())    // no base directory. can't check
                return FormValidation.ok();

            FilePath path = child(value);
            if(path.exists()) {
                if (expectingFile) {
                    if(!path.isDirectory())
                        return FormValidation.ok();
                    else
S
sogabe 已提交
2399
                        return FormValidation.error(Messages.FilePath_validateRelativePath_notFile(value));
K
kohsuke 已提交
2400 2401 2402 2403
                } else {
                    if(path.isDirectory())
                        return FormValidation.ok();
                    else
S
sogabe 已提交
2404
                        return FormValidation.error(Messages.FilePath_validateRelativePath_notDirectory(value));
K
kohsuke 已提交
2405 2406 2407
                }
            }

S
sogabe 已提交
2408 2409
            String msg = expectingFile ? Messages.FilePath_validateRelativePath_noSuchFile(value) : 
                Messages.FilePath_validateRelativePath_noSuchDirectory(value);
K
kohsuke 已提交
2410 2411 2412 2413 2414 2415 2416
            if(errorIfNotExist)     return FormValidation.error(msg);
            else                    return FormValidation.warning(msg);
        } catch (InterruptedException e) {
            return FormValidation.ok();
        }
    }

2417 2418 2419
    private static void checkPermissionForValidate() {
        AccessControlled subject = Stapler.getCurrentRequest().findAncestorObject(AbstractProject.class);
        if (subject == null)
2420
            Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
2421 2422 2423 2424
        else
            subject.checkPermission(Item.CONFIGURE);
    }

K
kohsuke 已提交
2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435
    /**
     * A convenience method over {@link #validateRelativePath(String, boolean, boolean)}.
     */
    public FormValidation validateRelativeDirectory(String value, boolean errorIfNotExist) throws IOException {
        return validateRelativePath(value,errorIfNotExist,false);
    }

    public FormValidation validateRelativeDirectory(String value) throws IOException {
        return validateRelativeDirectory(value,true);
    }

2436
    @Deprecated @Override
K
kohsuke 已提交
2437 2438
    public String toString() {
        // to make writing JSPs easily, return local
K
kohsuke 已提交
2439
        return remote;
K
kohsuke 已提交
2440 2441
    }

K
kohsuke 已提交
2442 2443
    public VirtualChannel getChannel() {
        if(channel!=null)   return channel;
2444
        else                return Jenkins.MasterComputer.localChannel;
K
kohsuke 已提交
2445 2446
    }

K
kohsuke 已提交
2447 2448 2449 2450
    /**
     * Returns true if this {@link FilePath} represents a remote file. 
     */
    public boolean isRemote() {
K
typo.  
kohsuke 已提交
2451
        return channel!=null;
K
kohsuke 已提交
2452 2453
    }

K
kohsuke 已提交
2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470
    private void writeObject(ObjectOutputStream oos) throws IOException {
        Channel target = Channel.current();

        if(channel!=null && channel!=target)
            throw new IllegalStateException("Can't send a remote FilePath to a different remote channel");

        oos.defaultWriteObject();
        oos.writeBoolean(channel==null);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        Channel channel = Channel.current();
        assert channel!=null;

        ois.defaultReadObject();
        if(ois.readBoolean()) {
            this.channel = channel;
2471
            this.filter = null;
K
kohsuke 已提交
2472 2473
        } else {
            this.channel = null;
2474 2475 2476 2477 2478
            // If the remote channel wants us to create a FilePath that points to a local file,
            // we need to make sure the access control takes place.
            // This covers the immediate case of FileCallables taking FilePath into reference closure implicitly,
            // but it also covers more general case of FilePath sent as a return value or argument.
            this.filter = FilePathFilter.current();
K
kohsuke 已提交
2479 2480 2481 2482
        }
    }

    private static final long serialVersionUID = 1L;
2483

2484 2485
    public static int SIDE_BUFFER_SIZE = 1024;

R
rseguy 已提交
2486 2487
    private static final Logger LOGGER = Logger.getLogger(FilePath.class.getName());

2488 2489 2490 2491 2492
    /**
     * Adapts {@link FileCallable} to {@link Callable}.
     */
    private class FileCallableWrapper<T> implements DelegatingCallable<T,IOException> {
        private final FileCallable<T> callable;
2493
        private transient ClassLoader classLoader;
2494 2495 2496

        public FileCallableWrapper(FileCallable<T> callable) {
            this.callable = callable;
2497 2498 2499 2500 2501 2502
            this.classLoader = callable.getClass().getClassLoader();
        }

        private FileCallableWrapper(FileCallable<T> callable, ClassLoader classLoader) {
            this.callable = callable;
            this.classLoader = classLoader;
2503 2504 2505
        }

        public T call() throws IOException {
2506 2507 2508 2509 2510
            try {
                return callable.invoke(new File(remote), Channel.current());
            } catch (InterruptedException e) {
                throw new TunneledInterruptedException(e);
            }
2511 2512
        }

2513 2514 2515 2516
        /**
         * Role check comes from {@link FileCallable}s.
         */
        @Override
2517 2518
        public void checkRoles(RoleChecker checker) throws SecurityException {
            callable.checkRoles(checker);
2519 2520
        }

2521
        public ClassLoader getClassLoader() {
2522
            return classLoader;
2523 2524 2525 2526
        }

        private static final long serialVersionUID = 1L;
    }
2527

2528 2529 2530 2531 2532 2533 2534 2535 2536 2537
    /**
     * Used to tunnel {@link InterruptedException} over a Java signature that only allows {@link IOException}
     */
    private static class TunneledInterruptedException extends IOException2 {
        private TunneledInterruptedException(InterruptedException cause) {
            super(cause);
        }
        private static final long serialVersionUID = 1L;
    }

2538 2539 2540 2541 2542
    private static final Comparator<String> SHORTER_STRING_FIRST = new Comparator<String>() {
        public int compare(String o1, String o2) {
            return o1.length()-o2.length();
        }
    };
K
Kohsuke Kawaguchi 已提交
2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556

    private static final Field LINKFLAG_FIELD = getTarEntryLinkFlagField();

    private static Field getTarEntryLinkFlagField() {
        try {
            Field f = TarEntry.class.getDeclaredField("linkFlag");
            f.setAccessible(true);
            return f;
        } catch (SecurityException e) {
            throw new AssertionError(e);
        } catch (NoSuchFieldException e) {
            throw new AssertionError(e);
        }
    }
K
Kohsuke Kawaguchi 已提交
2557 2558 2559 2560 2561 2562

    /**
     * Gets the {@link FilePath} representation of the "~" directory
     * (User's home directory in the Unix sense) of the given channel.
     */
    public static FilePath getHomeDirectory(VirtualChannel ch) throws InterruptedException, IOException {
2563
        return ch.call(new MasterToSlaveCallable<FilePath,IOException>() {
K
Kohsuke Kawaguchi 已提交
2564 2565 2566 2567 2568
            public FilePath call() throws IOException {
                return new FilePath(new File(System.getProperty("user.home")));
            }
        });
    }
2569 2570 2571

    /**
     * Helper class to make it easy to send an explicit list of files using {@link FilePath} methods.
K
Kohsuke Kawaguchi 已提交
2572
     * @since 1.532
2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596
     */
    public static final class ExplicitlySpecifiedDirScanner extends DirScanner {

        private static final long serialVersionUID = 1;

        private final Map<String,String> files;

        /**
         * Create a “scanner” (it actually does no scanning).
         * @param files a map from logical relative paths as per {@link FileVisitor#visit}, to actual relative paths within the scanned directory
         */
        public ExplicitlySpecifiedDirScanner(Map<String,String> files) {
            this.files = files;
        }

        @Override public void scan(File dir, FileVisitor visitor) throws IOException {
            for (Map.Entry<String,String> entry : files.entrySet()) {
                String archivedPath = entry.getKey();
                assert archivedPath.indexOf('\\') == -1;
                String workspacePath = entry.getValue();
                assert workspacePath.indexOf('\\') == -1;
                scanSingle(new File(dir, workspacePath), archivedPath, visitor);
            }
        }
2597
    }
2598

2599 2600
    private @Nonnull FilePathFilter filterNonNull() {
        return filter!=null ? filter : FilePathFilter.EMPTY;
2601 2602
    }

2603 2604 2605
    /**
     * Wraps {@link FileVisitor} to notify read access to {@link FilePathFilter}.
     */
2606
    private FileVisitor reading(final FileVisitor v) {
2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618
        final FilePathFilter filter = FilePathFilter.current();
        if (filter==null)    return v;

        return new FileVisitor() {
            @Override
            public void visit(File f, String relativePath) throws IOException {
                filter.read(f);
                v.visit(f,relativePath);
            }

            @Override
            public void visitSymlink(File link, String target, String relativePath) throws IOException {
K
Fix up.  
Kohsuke Kawaguchi 已提交
2619
                filter.read(link);
2620 2621 2622 2623 2624
                v.visitSymlink(link, target, relativePath);
            }

            @Override
            public boolean understandsSymlink() {
K
Fix up.  
Kohsuke Kawaguchi 已提交
2625
                return v.understandsSymlink();
2626 2627 2628 2629 2630 2631 2632
            }
        };
    }

    /**
     * Pass through 'f' after ensuring that we can read that file.
     */
2633 2634
    private File reading(File f) {
        filterNonNull().read(f);
2635 2636 2637 2638 2639 2640
        return f;
    }

    /**
     * Pass through 'f' after ensuring that we can access the file attributes.
     */
2641 2642
    private File stating(File f) {
        filterNonNull().stat(f);
2643 2644 2645 2646 2647 2648
        return f;
    }

    /**
     * Pass through 'f' after ensuring that we can create that file/dir.
     */
2649 2650
    private File creating(File f) {
        filterNonNull().create(f);
2651 2652 2653 2654 2655 2656
        return f;
    }

    /**
     * Pass through 'f' after ensuring that we can write to that file.
     */
2657 2658
    private File writing(File f) {
        FilePathFilter filter = filterNonNull();
K
Fix up.  
Kohsuke Kawaguchi 已提交
2659 2660 2661
        if (!f.exists())
            filter.create(f);
        filter.write(f);
2662 2663 2664 2665
        return f;
    }

    /**
2666
     * Pass through 'f' after ensuring that we can delete that file.
2667
     */
2668 2669
    private File deleting(File f) {
        filterNonNull().delete(f);
2670 2671 2672 2673
        return f;
    }


2674
    private boolean mkdirs(File dir) {
K
Fix up.  
Kohsuke Kawaguchi 已提交
2675 2676
        if (dir.exists())   return false;

2677
        filterNonNull().mkdirs(dir);
2678 2679
        return dir.mkdirs();
    }
K
kohsuke 已提交
2680
}