FilePath.java 110.4 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;

K
Kohsuke Kawaguchi 已提交
28 29
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.GZIPOutputStream;
30 31
import hudson.Launcher.LocalLauncher;
import hudson.Launcher.RemoteLauncher;
K
kohsuke 已提交
32
import hudson.model.AbstractProject;
K
Kohsuke Kawaguchi 已提交
33
import hudson.model.Computer;
K
kohsuke 已提交
34
import hudson.model.Item;
K
Kohsuke Kawaguchi 已提交
35 36 37
import hudson.model.TaskListener;
import hudson.os.PosixAPI;
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 Kawaguchi 已提交
42
import hudson.remoting.LocalChannel;
K
kohsuke 已提交
43
import hudson.remoting.Pipe;
K
Kohsuke Kawaguchi 已提交
44
import hudson.remoting.RemoteInputStream;
45
import hudson.remoting.RemoteInputStream.Flag;
K
kohsuke 已提交
46 47
import hudson.remoting.RemoteOutputStream;
import hudson.remoting.VirtualChannel;
48
import hudson.remoting.Which;
49
import hudson.security.AccessControlled;
K
Kohsuke Kawaguchi 已提交
50
import hudson.util.DaemonThreadFactory;
51
import hudson.util.DirScanner;
K
Kohsuke Kawaguchi 已提交
52 53
import hudson.util.ExceptionCatchingThreadFactory;
import hudson.util.FileVisitor;
K
kohsuke 已提交
54
import hudson.util.FormValidation;
K
Kohsuke Kawaguchi 已提交
55
import hudson.util.HeadBufferingStream;
K
kohsuke 已提交
56
import hudson.util.IOUtils;
K
Kohsuke Kawaguchi 已提交
57
import hudson.util.NamingThreadFactory;
58 59
import hudson.util.io.Archiver;
import hudson.util.io.ArchiverFactory;
60
import jenkins.FilePathFilter;
61 62
import jenkins.MasterToSlaveFileCallable;
import jenkins.SlaveToMasterFileCallable;
63
import jenkins.SoloFilePathFilter;
K
Kohsuke Kawaguchi 已提交
64 65
import jenkins.model.Jenkins;
import jenkins.util.ContextResettingExecutorService;
66
import jenkins.util.VirtualFile;
K
Kohsuke Kawaguchi 已提交
67 68
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.input.CountingInputStream;
K
kohsuke 已提交
69
import org.apache.tools.ant.DirectoryScanner;
70
import org.apache.tools.ant.Project;
K
kohsuke 已提交
71
import org.apache.tools.ant.types.FileSet;
K
Kohsuke Kawaguchi 已提交
72 73
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
K
kohsuke 已提交
74
import org.kohsuke.stapler.Stapler;
K
Kohsuke Kawaguchi 已提交
75

K
Kohsuke Kawaguchi 已提交
76
import javax.annotation.CheckForNull;
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;
K
Kohsuke Kawaguchi 已提交
89
import java.io.OutputStreamWriter;
90
import java.io.RandomAccessFile;
K
kohsuke 已提交
91 92
import java.io.Serializable;
import java.io.Writer;
K
Kohsuke Kawaguchi 已提交
93
import java.lang.reflect.Field;
94
import java.net.HttpURLConnection;
95
import java.net.URI;
K
kohsuke 已提交
96
import java.net.URL;
K
kohsuke 已提交
97
import java.net.URLConnection;
K
kohsuke 已提交
98
import java.util.ArrayList;
99 100
import java.util.Arrays;
import java.util.Comparator;
K
Kohsuke Kawaguchi 已提交
101 102 103 104
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
105
import java.util.concurrent.ExecutionException;
K
Kohsuke Kawaguchi 已提交
106 107
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
108 109
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
110
import java.util.concurrent.atomic.AtomicInteger;
K
Kohsuke Kawaguchi 已提交
111
import java.util.logging.Level;
R
rseguy 已提交
112
import java.util.logging.Logger;
K
Kohsuke Kawaguchi 已提交
113 114
import java.util.regex.Matcher;
import java.util.regex.Pattern;
K
kohsuke 已提交
115

K
Kohsuke Kawaguchi 已提交
116 117
import static hudson.FilePath.TarCompression.*;
import static hudson.Util.*;
118
import javax.annotation.Nonnull;
119 120
import javax.annotation.Nullable;
import jenkins.security.MasterToSlaveCallable;
121 122
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
123 124
import org.jenkinsci.remoting.RoleChecker;
import org.jenkinsci.remoting.RoleSensitive;
125
        
K
kohsuke 已提交
126
/**
K
kohsuke 已提交
127
 * {@link File} like object with remoting support.
K
kohsuke 已提交
128 129
 *
 * <p>
K
kohsuke 已提交
130 131 132 133 134 135 136 137 138 139 140 141 142
 * 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 已提交
143
 * layer.
K
kohsuke 已提交
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
 *
 * <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>
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
 * 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 已提交
175
 * </pre>
K
kohsuke 已提交
176 177
 *
 * <p>
K
kohsuke 已提交
178
 * When {@link FileCallable} is transfered to a remote node, it will be done so
E
elefevre 已提交
179
 * by using the same Java serialization scheme that the remoting module uses.
K
kohsuke 已提交
180 181 182 183 184 185 186
 * 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 已提交
187 188
 *
 * @author Kohsuke Kawaguchi
K
Kohsuke Kawaguchi 已提交
189
 * @see VirtualFile
K
kohsuke 已提交
190
 */
K
kohsuke 已提交
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
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 已提交
208 209
    private final String remote;

210 211 212 213 214 215 216 217 218
    /**
     * 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()
     */
219 220
    private transient @Nullable
    SoloFilePathFilter filter;
221

K
kohsuke 已提交
222 223 224 225 226 227 228
    /**
     * 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 已提交
229
    public FilePath(VirtualChannel channel, String remote) {
K
Kohsuke Kawaguchi 已提交
230
        this.channel = channel instanceof LocalChannel ? null : channel;
231
        this.remote = normalize(remote);
K
kohsuke 已提交
232 233 234
    }

    /**
K
kohsuke 已提交
235 236 237 238 239
     * 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 已提交
240
     */
K
kohsuke 已提交
241 242
    public FilePath(File localPath) {
        this.channel = null;
243
        this.remote = normalize(localPath.getPath());
K
kohsuke 已提交
244 245
    }

K
kohsuke 已提交
246 247 248 249 250
    /**
     * 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 已提交
251
    public FilePath(FilePath base, String rel) {
K
kohsuke 已提交
252
        this.channel = base.channel;
253 254 255 256 257
        this.remote = normalize(resolvePathIfRelative(base, rel));
    }

    private String resolvePathIfRelative(FilePath base, String rel) {
        if(isAbsolute(rel)) return rel;
K
kohsuke 已提交
258
        if(base.isUnix()) {
259
            // shouldn't need this replace, but better safe than sorry
260
            return base.remote+'/'+rel.replace('\\','/');
K
kohsuke 已提交
261
        } else {
262 263
            // 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
264
            return base.remote+'\\'+rel.replace('/','\\');
K
kohsuke 已提交
265 266 267
        }
    }

K
Kohsuke Kawaguchi 已提交
268 269 270
    /**
     * Is the given path name an absolute path?
     */
K
kohsuke 已提交
271
    private static boolean isAbsolute(String rel) {
272
        return rel.startsWith("/") || DRIVE_PATTERN.matcher(rel).matches() || UNC_PATTERN.matcher(rel).matches();
K
kohsuke 已提交
273 274
    }

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

279 280 281 282 283
    /**
     * {@link File#getParent()} etc cannot handle ".." and "." in the path component very well,
     * so remove them.
     */
    private static String normalize(String path) {
284 285 286 287 288 289 290 291 292
        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
293
        List<String> tokens = new ArrayList<String>();
294 295 296 297 298 299 300 301 302 303 304
        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;
305 306
            }
        }
307 308 309
        if (s < end) tokens.add(path.substring(s));
        // Look through tokens for "." or ".."
        for (int i = 0; i < tokens.size();) {
310 311 312
            String token = tokens.get(i);
            if (token.equals(".")) {
                tokens.remove(i);
313 314 315 316 317 318 319 320 321 322 323 324
                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;
325
                    for (int j = 0; j < 3; j++) tokens.remove(i);
326 327 328
                    if (i > 0) tokens.remove(i-1);
                    else if (tokens.size() > 0) tokens.remove(0);
                }
329
            } else
330
                i += 2;
331
        }
332 333 334 335
        // Recombine tokens
        for (String token : tokens) buf.append(token);
        if (buf.length() == 0) buf.append('.');
        return buf.toString();
336 337
    }

K
kohsuke 已提交
338 339 340
    /**
     * Checks if the remote path is Unix.
     */
341
    boolean isUnix() {
342 343 344 345
        // if the path represents a local path, there' no need to guess.
        if(!isRemote())
            return File.pathSeparatorChar!=';';
            
346 347 348 349 350 351
        // 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 已提交
352 353 354 355 356
        // 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ø 已提交
357 358 359 360
    /**
     * Gets the full path of the file on the remote machine.
     *
     */
K
kohsuke 已提交
361 362 363 364
    public String getRemote() {
        return remote;
    }

365 366
    /**
     * Creates a zip file from this directory or a file and sends that to the given output stream.
367 368
     *
     * @deprecated as of 1.315. Use {@link #zip(OutputStream)} that has more consistent name.
369 370
     */
    public void createZipArchive(OutputStream os) throws IOException, InterruptedException {
371 372
        zip(os);
    }
373

374 375 376 377 378 379
    /**
     * 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);
    }
380

K
Kohsuke Kawaguchi 已提交
381 382 383 384 385 386 387 388 389
    public void zip(FilePath dst) throws IOException, InterruptedException {
        OutputStream os = dst.write();
        try {
            zip(os);
        } finally {
            os.close();
        }
    }
    
390 391 392 393 394 395 396 397 398 399
    /**
     * 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 {
400
        archive(ArchiverFactory.ZIP,os,filter);
401 402
    }

K
kohsuke 已提交
403 404 405 406 407 408 409 410
    /**
     * 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
411 412
     * @deprecated as of 1.315
     *      Use {@link #zip(OutputStream,String)} that has more consistent name.
K
kohsuke 已提交
413 414
     */
    public void createZipArchive(OutputStream os, final String glob) throws IOException, InterruptedException {
415
        archive(ArchiverFactory.ZIP,os,glob);
416 417 418 419 420 421 422
    }

    /**
     * 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
423
     *      works like {@link #createZipArchive(OutputStream)}, inserting a top-level directory into the ZIP.
424 425 426 427
     *
     * @since 1.315
     */
    public void zip(OutputStream os, final String glob) throws IOException, InterruptedException {
428
        archive(ArchiverFactory.ZIP,os,glob);
429 430
    }

431 432 433 434
    /**
     * 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 {
435
        return archive(ArchiverFactory.ZIP, out, scanner);
436 437
    }

438 439 440 441 442 443 444 445
    /**
     * 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.
     */
446
    public int archive(final ArchiverFactory factory, OutputStream os, final DirScanner scanner) throws IOException, InterruptedException {
K
kohsuke 已提交
447
        final OutputStream out = (channel!=null)?new RemoteOutputStream(os):os;
448
        return act(new SecureFileCallable<Integer>() {
449 450 451
            public Integer invoke(File f, VirtualChannel channel) throws IOException {
                Archiver a = factory.create(out);
                try {
452
                    scanner.scan(f,reading(a));
453 454
                } finally {
                    a.close();
K
kohsuke 已提交
455
                }
456
                return a.countEntries();
K
kohsuke 已提交
457 458 459 460 461 462
            }

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

K
Kohsuke Kawaguchi 已提交
463
    public int archive(final ArchiverFactory factory, OutputStream os, final FileFilter filter) throws IOException, InterruptedException {
464 465 466
        return archive(factory,os,new DirScanner.Filter(filter));
    }

K
Kohsuke Kawaguchi 已提交
467
    public int archive(final ArchiverFactory factory, OutputStream os, final String glob) throws IOException, InterruptedException {
468 469 470
        return archive(factory,os,new DirScanner.Glob(glob,null));
    }

K
kohsuke 已提交
471 472 473 474 475 476
    /**
     * 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 已提交
477
     * @see #unzipFrom(InputStream)
K
kohsuke 已提交
478
     */
K
kohsuke 已提交
479
    public void unzip(final FilePath target) throws IOException, InterruptedException {
480
        // TODO: post release, re-unite two branches by introducing FileStreamCallable that resolves InputStream
481 482 483 484 485 486 487
        if (this.channel!=target.channel) {// local -> remote or remote->local
            final RemoteInputStream in = new RemoteInputStream(read(), Flag.GREEDY);
            target.act(new SecureFileCallable<Void>() {
                public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
                    unzip(dir, in);
                    return null;
                }
488

489 490 491 492
                private static final long serialVersionUID = 1L;
            });
        } else {// local -> local or remote->remote
            target.act(new SecureFileCallable<Void>() {
493

494 495
                public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
                    assert !FilePath.this.isRemote();       // this.channel==target.channel above
496
                    unzip(dir, reading(new File(FilePath.this.getRemote()))); // shortcut to local file
497 498 499 500 501 502
                    return null;
                }

                private static final long serialVersionUID = 1L;
            });
        }
K
kohsuke 已提交
503
    }
K
kohsuke 已提交
504

K
kohsuke 已提交
505 506 507 508 509 510 511 512 513 514 515
    /**
     * 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 {
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
        // TODO: post release, re-unite two branches by introducing FileStreamCallable that resolves InputStream
        if (this.channel!=target.channel) {// local -> remote or remote->local
            final RemoteInputStream in = new RemoteInputStream(read(), Flag.GREEDY);
            target.act(new SecureFileCallable<Void>() {
                public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
                    readFromTar(FilePath.this.getName(),dir,compression.extract(in));
                    return null;
                }

                private static final long serialVersionUID = 1L;
            });
        } else {// local -> local or remote->remote
            target.act(new SecureFileCallable<Void>() {
                public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
                    readFromTar(FilePath.this.getName(),dir,compression.extract(FilePath.this.read()));
                    return null;
                }
                private static final long serialVersionUID = 1L;
            });
        }
K
kohsuke 已提交
536 537
    }

K
kohsuke 已提交
538 539 540 541 542 543
    /**
     * 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 已提交
544
     * @see #unzip(FilePath)
K
kohsuke 已提交
545 546 547
     */
    public void unzipFrom(InputStream _in) throws IOException, InterruptedException {
        final InputStream in = new RemoteInputStream(_in);
548
        act(new SecureFileCallable<Void>() {
K
kohsuke 已提交
549 550 551 552
            public Void invoke(File dir, VirtualChannel channel) throws IOException {
                unzip(dir, in);
                return null;
            }
K
kohsuke 已提交
553 554 555 556
            private static final long serialVersionUID = 1L;
        });
    }

557
    private void unzip(File dir, InputStream in) throws IOException {
558 559
        File tmpFile = File.createTempFile("tmpzip", null); // uses java.io.tmpdir
        try {
560
            // TODO why does this not simply use ZipInputStream?
561 562 563 564 565 566 567 568
            IOUtils.copy(in, tmpFile);
            unzip(dir,tmpFile);
        }
        finally {
            tmpFile.delete();
        }
    }

569
    private void unzip(File dir, File zipFile) throws IOException {
K
kohsuke 已提交
570
        dir = dir.getAbsoluteFile();    // without absolutization, getParentFile below seems to fail
571
        ZipFile zip = new ZipFile(zipFile);
C
Christoph Kutzinski 已提交
572
        @SuppressWarnings("unchecked")
573
        Enumeration<ZipEntry> entries = zip.getEntries();
K
kohsuke 已提交
574 575

        try {
576 577 578 579
            while (entries.hasMoreElements()) {
                ZipEntry e = entries.nextElement();
                File f = new File(dir, e.getName());
                if (e.isDirectory()) {
580
                    mkdirs(f);
K
kohsuke 已提交
581 582
                } else {
                    File p = f.getParentFile();
583
                    if (p != null) {
584
                        mkdirs(p);
585
                    }
586 587
                    InputStream input = zip.getInputStream(e);
                    try {
588
                        IOUtils.copy(input, writing(f));
589 590 591
                    } finally {
                        input.close();
                    }
592 593
                    try {
                        FilePath target = new FilePath(f);
594 595 596
                        int mode = e.getUnixMode();
                        if (mode!=0)    // Ant returns 0 if the archive doesn't record the access mode
                            target.chmod(mode);
597 598 599
                    } catch (InterruptedException ex) {
                        LOGGER.log(Level.WARNING, "unable to set permissions", ex);
                    }
600
                    f.setLastModified(e.getTime());
K
kohsuke 已提交
601 602 603 604 605 606 607
                }
            }
        } finally {
            zip.close();
        }
    }

K
kohsuke 已提交
608 609 610 611
    /**
     * Absolutizes this {@link FilePath} and returns the new one.
     */
    public FilePath absolutize() throws IOException, InterruptedException {
612
        return new FilePath(channel,act(new SecureFileCallable<String>() {
613
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
614 615 616 617 618 619
            public String invoke(File f, VirtualChannel channel) throws IOException {
                return f.getAbsolutePath();
            }
        }));
    }

K
Kohsuke Kawaguchi 已提交
620 621 622 623 624 625 626 627 628 629
    /**
     * 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 {
630
        act(new SecureFileCallable<Void>() {
631
            private static final long serialVersionUID = 1L;
K
Kohsuke Kawaguchi 已提交
632
            public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
633
                symlinking(f);
K
Kohsuke Kawaguchi 已提交
634 635 636 637 638 639 640 641 642 643 644 645 646 647
                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 {
648
        return act(new SecureFileCallable<String>() {
649
            private static final long serialVersionUID = 1L;
K
Kohsuke Kawaguchi 已提交
650
            public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
651
                return Util.resolveSymlink(reading(f));
K
Kohsuke Kawaguchi 已提交
652 653 654 655
            }
        });
    }

K
kohsuke 已提交
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
    @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 已提交
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
    /**
     * 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 {
689
                    return new GZIPInputStream(in, 8192, true);
K
kohsuke 已提交
690 691 692
                } catch (IOException e) {
                    // various people reported "java.io.IOException: Not in GZIP format" here, so diagnose this problem better
                    in.fillSide();
693
                    throw new IOException(e.getMessage()+"\nstream="+Util.toHexString(in.getSideBuffer()),e);
K
kohsuke 已提交
694 695 696
                }
            }
            public OutputStream compress(OutputStream out) throws IOException {
697
                return new GZIPOutputStream(new BufferedOutputStream(out));
K
kohsuke 已提交
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
            }
        };

        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);
717
            act(new SecureFileCallable<Void>() {
K
kohsuke 已提交
718
                public Void invoke(File dir, VirtualChannel channel) throws IOException {
719
                    readFromTar("input stream",dir, compression.extract(in));
K
kohsuke 已提交
720 721 722 723 724
                    return null;
                }
                private static final long serialVersionUID = 1L;
            });
        } finally {
725
            org.apache.commons.io.IOUtils.closeQuietly(_in);
K
kohsuke 已提交
726 727 728
        }
    }

K
kohsuke 已提交
729
    /**
K
kohsuke 已提交
730
     * Given a tgz/zip file, extracts it to the given target directory, if necessary.
K
kohsuke 已提交
731 732 733 734 735 736
     *
     * <p>
     * This method is a convenience method designed for installing a binary package to a location
     * that supports upgrade and downgrade. Specifically,
     *
     * <ul>
737 738 739
     * <li>If the target directory doesn't exist {@linkplain #mkdirs() it will be created}.
     * <li>The timestamp of the archive is left in the installation directory upon extraction.
     * <li>If the timestamp left in the directory does not match the timestamp of the current archive file,
K
kohsuke 已提交
740
     *     the directory contents will be discarded and the archive file will be re-extracted.
K
kohsuke 已提交
741
     * <li>If the connection is refused but the target directory already exists, it is left alone.
K
kohsuke 已提交
742 743
     * </ul>
     *
K
kohsuke 已提交
744
     * @param archive
745 746
     *      The resource that represents the tgz/zip file. This URL must support the {@code Last-Modified} header.
     *      (For example, you could use {@link ClassLoader#getResource}.)
K
kohsuke 已提交
747 748
     * @param listener
     *      If non-null, a message will be printed to this listener once this method decides to
749 750
     *      extract an archive, or if there is any issue.
     * @param message a message to be printed in case extraction will proceed.
K
kohsuke 已提交
751 752 753 754 755
     * @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
     */
756
    public boolean installIfNecessaryFrom(@Nonnull URL archive, @CheckForNull TaskListener listener, @Nonnull String message) throws IOException, InterruptedException {
K
kohsuke 已提交
757
        try {
758
            FilePath timestamp = this.child(".timestamp");
759
            long lastModified = timestamp.lastModified();
760 761
            URLConnection con;
            try {
762
                con = ProxyConfiguration.open(archive);
763 764
                if (lastModified != 0) {
                    con.setIfModifiedSince(lastModified);
765
                }
766 767 768 769 770 771 772 773 774 775
                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 已提交
776 777
                }
            }
778

779 780 781 782 783 784 785 786 787
            if (lastModified != 0 && con instanceof HttpURLConnection) {
                HttpURLConnection httpCon = (HttpURLConnection) con;
                int responseCode = httpCon.getResponseCode();
                if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                    return false;
                } else if (responseCode != HttpURLConnection.HTTP_OK) {
                    listener.getLogger().println("Skipping installation of " + archive + " to " + remote + " due to server error: " + responseCode + " " + httpCon.getResponseMessage());
                    return false;
                }
788 789
            }

790
            long sourceTimestamp = con.getLastModified();
K
kohsuke 已提交
791

792
            if(this.exists()) {
793
                if (lastModified != 0 && sourceTimestamp == lastModified)
794 795 796 797 798
                    return false;   // already up to date
                this.deleteContents();
            } else {
                this.mkdirs();
            }
K
kohsuke 已提交
799

800 801 802
            if(listener!=null)
                listener.getLogger().println(message);

803 804 805 806 807 808 809 810 811 812 813 814 815
            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"));
                    }
                }
            }

816
            // for HTTP downloads, enable automatic retry for added resilience
817
            InputStream in = archive.getProtocol().startsWith("http") ? ProxyConfiguration.getInputStream(archive) : con.getInputStream();
818 819 820 821
            CountingInputStream cis = new CountingInputStream(in);
            try {
                if(archive.toExternalForm().endsWith(".zip"))
                    unzipFrom(cis);
822 823
                else
                    untarFrom(cis,GZIP);
824
            } catch (IOException e) {
825
                throw new IOException(String.format("Failed to unpack %s (%d bytes read of total %d)",
826 827 828 829
                        archive,cis.getByteCount(),con.getContentLength()),e);
            }
            timestamp.touch(sourceTimestamp);
            return true;
K
kohsuke 已提交
830
        } catch (IOException e) {
831
            throw new IOException("Failed to install "+archive+" to "+remote,e);
K
kohsuke 已提交
832
        }
K
kohsuke 已提交
833 834
    }

835 836
    // this reads from arbitrary URL
    private final class Unpack extends MasterToSlaveFileCallable<Void> {
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
        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) {
852
                    throw new IOException(String.format("Failed to unpack %s (%d bytes read)", archive, cis.getByteCount()), x);
853 854 855 856 857 858 859 860
                }
            } finally {
                in.close();
            }
            return null;
        }
    }

K
kohsuke 已提交
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883
    /**
     * 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 {
884
            org.apache.commons.io.IOUtils.copy(in, os);
K
kohsuke 已提交
885 886 887 888
        } finally {
            os.close();
        }
    }
K
kohsuke 已提交
889 890

    /**
C
Christoph Kutzinski 已提交
891
     * Convenience method to call {@link FilePath#copyTo(FilePath)}.
K
kohsuke 已提交
892 893 894 895 896 897
     * 
     * @since 1.311
     */
    public void copyFrom(FilePath src) throws IOException, InterruptedException {
        src.copyTo(this);
    }
K
kohsuke 已提交
898

899 900 901 902 903 904
    /**
     * 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 {
905
                file.write(writing(new File(remote)));
906 907 908
            } catch (IOException e) {
                throw e;
            } catch (Exception e) {
909
                throw new IOException(e);
910 911 912 913 914
            }
        } else {
            InputStream i = file.getInputStream();
            OutputStream o = write();
            try {
915
                org.apache.commons.io.IOUtils.copy(i,o);
916
            } finally {
917 918 919 920 921
                try {
                    o.close();
                } finally {
                    i.close();
                }
922 923 924 925
            }
        }
    }

K
kohsuke 已提交
926 927 928
    /**
     * Code that gets executed on the machine where the {@link FilePath} is local.
     * Used to act on {@link FilePath}.
929
     * <strong>Warning:</code> implementations must be serializable, so prefer a static nested class to an inner class.
930 931 932 933 934
     *
     * <p>
     * Subtypes would likely want to extend from either {@link MasterToSlaveCallable}
     * or {@link SlaveToMasterFileCallable}.
     *
K
kohsuke 已提交
935 936
     * @see FilePath#act(FileCallable)
     */
937
    public interface FileCallable<T> extends Serializable, RoleSensitive {
K
kohsuke 已提交
938 939 940
        /**
         * Performs the computational task on the node where the data is located.
         *
941 942 943
         * <p>
         * All the exceptions are forwarded to the caller.
         *
K
kohsuke 已提交
944 945 946 947 948 949
         * @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.
         */
950
        T invoke(File f, VirtualChannel channel) throws IOException, InterruptedException;
K
kohsuke 已提交
951 952
    }

953 954 955 956 957 958 959 960 961
    /**
     * {@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 已提交
962 963 964 965 966
    /**
     * 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 {
967 968 969 970
        return act(callable,callable.getClass().getClassLoader());
    }

    private <T> T act(final FileCallable<T> callable, ClassLoader cl) throws IOException, InterruptedException {
K
kohsuke 已提交
971 972 973
        if(channel!=null) {
            // run this on a remote system
            try {
974
                DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<T>(callable, cl);
975 976
                for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) {
                    wrapper = factory.wrap(wrapper);
977 978
                }
                return channel.call(wrapper);
979
            } catch (TunneledInterruptedException e) {
980
                throw (InterruptedException)new InterruptedException(e.getMessage()).initCause(e);
K
kohsuke 已提交
981 982
            } catch (AbortException e) {
                throw e;    // pass through so that the caller can catch it as AbortException
K
kohsuke 已提交
983 984
            } catch (IOException e) {
                // wrap it into a new IOException so that we get the caller's stack trace as well.
985
                throw new IOException("remote file operation failed: " + remote + " at " + channel + ": " + e, e);
K
kohsuke 已提交
986 987 988
            }
        } else {
            // the file is on the local machine.
K
Kohsuke Kawaguchi 已提交
989
            return callable.invoke(new File(remote), localChannel);
K
kohsuke 已提交
990 991 992
        }
    }

993 994
    /**
     * This extension point allows to contribute a wrapper around a fileCallable so that a plugin can "intercept" a
995 996 997
     * 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.
998 999 1000
     *
     * @since 1.482
     * @see AbstractInterceptorCallableWrapper
1001
     */
1002
    public static abstract class FileCallableWrapperFactory implements ExtensionPoint {
1003 1004 1005 1006 1007

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

    }

1008 1009 1010
    /**
     * Abstract {@link DelegatingCallable} that exposes an Before/After pattern for
     * {@link hudson.FilePath.FileCallableWrapperFactory} that want to implement AOP-style interceptors
1011
     * @since 1.482
1012
     */
1013
    public static abstract class AbstractInterceptorCallableWrapper<T> implements DelegatingCallable<T, IOException> {
1014
        private static final long serialVersionUID = 1L;
1015

1016
        private final DelegatingCallable<T, IOException> callable;
1017 1018 1019 1020 1021

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

1022 1023
        @Override
        public final ClassLoader getClassLoader() {
1024 1025 1026
            return callable.getClassLoader();
        }

1027
        public final T call() throws IOException {
1028 1029 1030 1031 1032 1033 1034 1035
            before();
            try {
                return callable.call();
            } finally {
                after();
            }
        }

1036 1037 1038
        /**
         * Executed before the actual FileCallable is invoked. This code will run on remote
         */
1039
        protected void before() {}
1040 1041

        /**
1042
         * Executed after the actual FileCallable is invoked (even if this one failed). This code will run on remote
1043
         */
1044 1045 1046 1047
        protected void after() {}
    }


1048 1049 1050 1051 1052 1053
    /**
     * 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 已提交
1054
            DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<T>(callable);
1055 1056
            for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) {
                wrapper = factory.wrap(wrapper);
W
wolfgarnet 已提交
1057
            }
K
Kohsuke Kawaguchi 已提交
1058
            return (channel!=null ? channel : localChannel)
W
wolfgarnet 已提交
1059
                .callAsync(wrapper);
1060 1061
        } catch (IOException e) {
            // wrap it into a new IOException so that we get the caller's stack trace as well.
1062
            throw new IOException("remote file operation failed",e);
1063 1064 1065
        }
    }

K
kohsuke 已提交
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
    /**
     * 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 已提交
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096
    /**
     * 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);
                }
            }
1097 1098

            @Override
1099 1100
            public void checkRoles(RoleChecker checker) throws SecurityException {
                task.checkRoles(checker);
1101 1102
            }

K
Kohsuke Kawaguchi 已提交
1103 1104 1105 1106
            private static final long serialVersionUID = 1L;
        };
    }

K
kohsuke 已提交
1107 1108 1109 1110 1111
    /**
     * Converts this file to the URI, relative to the machine
     * on which this file is available.
     */
    public URI toURI() throws IOException, InterruptedException {
1112
        return act(new SecureFileCallable<URI>() {
1113
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1114 1115 1116 1117 1118 1119
            public URI invoke(File f, VirtualChannel channel) {
                return f.toURI();
            }
        });
    }

1120 1121
    /**
     * Gets the {@link VirtualFile} representation of this {@link FilePath}
K
Kohsuke Kawaguchi 已提交
1122 1123
     *
     * @since 1.532
1124 1125 1126 1127 1128
     */
    public VirtualFile toVirtualFile() {
        return VirtualFile.forFilePath(this);
    }

K
Kohsuke Kawaguchi 已提交
1129 1130 1131
    /**
     * If this {@link FilePath} represents a file on a particular {@link Computer}, return it.
     * Otherwise null.
J
Jesse Glick 已提交
1132
     * @since 1.571
K
Kohsuke Kawaguchi 已提交
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145
     */
    public @CheckForNull Computer toComputer() {
        Jenkins j = Jenkins.getInstance();
        if (j != null) {
            for (Computer c : j.getComputers()) {
                if (getChannel()==c.getChannel()) {
                    return c;
                }
            }
        }
        return null;
    }

K
kohsuke 已提交
1146 1147 1148
    /**
     * Creates this directory.
     */
K
kohsuke 已提交
1149
    public void mkdirs() throws IOException, InterruptedException {
1150
        if(!act(new SecureFileCallable<Boolean>() {
1151
            private static final long serialVersionUID = 1L;
1152
            public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
1153
                if(mkdirs(f) || f.exists())
1154 1155 1156
                    return true;    // OK

                // following Ant <mkdir> task to avoid possible race condition.
1157
                Thread.sleep(10);
1158 1159

                return f.mkdirs() || f.exists();
K
kohsuke 已提交
1160 1161 1162 1163 1164 1165 1166 1167 1168
            }
        }))
            throw new IOException("Failed to mkdirs: "+remote);
    }

    /**
     * Deletes this directory, including all its contents recursively.
     */
    public void deleteRecursive() throws IOException, InterruptedException {
1169
        act(new SecureFileCallable<Void>() {
1170
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1171
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1172
                deleteRecursive(deleting(f));
K
kohsuke 已提交
1173 1174 1175
                return null;
            }
        });
K
kohsuke 已提交
1176 1177 1178 1179 1180
    }

    /**
     * Deletes all the contents of this directory, but not the directory itself
     */
K
kohsuke 已提交
1181
    public void deleteContents() throws IOException, InterruptedException {
1182
        act(new SecureFileCallable<Void>() {
1183
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1184
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1185
                deleteContentsRecursive(f);
K
kohsuke 已提交
1186 1187 1188
                return null;
            }
        });
K
kohsuke 已提交
1189 1190
    }

1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
    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 已提交
1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224
    /**
     * 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 已提交
1225
    /**
K
Kohsuke Kawaguchi 已提交
1226
     * Gets just the file name portion without directories.
K
kohsuke 已提交
1227
     *
K
Kohsuke Kawaguchi 已提交
1228
     * For example, "foo.txt" for "../abc/foo.txt"
K
kohsuke 已提交
1229 1230
     */
    public String getName() {
1231 1232 1233 1234 1235
        String r = remote;
        if(r.endsWith("\\") || r.endsWith("/"))
            r = r.substring(0,r.length()-1);

        int len = r.length()-1;
K
kohsuke 已提交
1236
        while(len>=0) {
1237
            char ch = r.charAt(len);
K
kohsuke 已提交
1238 1239 1240 1241 1242
            if(ch=='\\' || ch=='/')
                break;
            len--;
        }

1243
        return r.substring(len+1);
K
kohsuke 已提交
1244 1245
    }

K
kohsuke 已提交
1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259
    /**
     * 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 已提交
1260
    /**
K
kohsuke 已提交
1261
     * The same as {@link FilePath#FilePath(FilePath,String)} but more OO.
1262
     * @param relOrAbsolute a relative or absolute path
K
kohsuke 已提交
1263
     * @return a file on the same channel
K
kohsuke 已提交
1264
     */
1265
    public @Nonnull FilePath child(String relOrAbsolute) {
1266
        return new FilePath(this,relOrAbsolute);
K
kohsuke 已提交
1267 1268 1269 1270
    }

    /**
     * Gets the parent file.
1271
     * @return parent FilePath or null if there is no parent
K
kohsuke 已提交
1272 1273
     */
    public FilePath getParent() {
1274 1275 1276
        int i = remote.length() - 2;
        for (; i >= 0; i--) {
            char ch = remote.charAt(i);
K
kohsuke 已提交
1277 1278 1279 1280
            if(ch=='\\' || ch=='/')
                break;
        }

1281
        return i >= 0 ? new FilePath( channel, remote.substring(0,i+1) ) : null;
K
kohsuke 已提交
1282 1283 1284
    }

    /**
K
kohsuke 已提交
1285
     * Creates a temporary file in the directory that this {@link FilePath} object designates.
J
Jørgen P. Tjernø 已提交
1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
     *
     * @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 已提交
1296
     */
K
kohsuke 已提交
1297 1298
    public FilePath createTempFile(final String prefix, final String suffix) throws IOException, InterruptedException {
        try {
1299
            return new FilePath(this,act(new SecureFileCallable<String>() {
1300
                private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1301
                public String invoke(File dir, VirtualChannel channel) throws IOException {
1302
                    File f = writing(File.createTempFile(prefix, suffix, dir));
K
kohsuke 已提交
1303 1304 1305 1306
                    return f.getName();
                }
            }));
        } catch (IOException e) {
1307
            throw new IOException("Failed to create a temp file on "+remote,e);
K
kohsuke 已提交
1308 1309 1310 1311
        }
    }

    /**
J
Jørgen P. Tjernø 已提交
1312
     * Creates a temporary file in this directory and set the contents to the
K
kohsuke 已提交
1313
     * given text (encoded in the platform default encoding)
J
Jørgen P. Tjernø 已提交
1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325
     *
     * @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 已提交
1326 1327
     */
    public FilePath createTextTempFile(final String prefix, final String suffix, final String contents) throws IOException, InterruptedException {
1328 1329 1330 1331
        return createTextTempFile(prefix,suffix,contents,true);
    }

    /**
J
Jørgen P. Tjernø 已提交
1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351
     * 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)
1352 1353
     */
    public FilePath createTextTempFile(final String prefix, final String suffix, final String contents, final boolean inThisDirectory) throws IOException, InterruptedException {
K
kohsuke 已提交
1354
        try {
1355
            return new FilePath(channel,act(new SecureFileCallable<String>() {
1356
                private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1357
                public String invoke(File dir, VirtualChannel channel) throws IOException {
1358
                    if(!inThisDirectory)
K
kohsuke 已提交
1359
                        dir = new File(System.getProperty("java.io.tmpdir"));
1360
                    else
1361
                        mkdirs(dir);
1362 1363 1364

                    File f;
                    try {
1365
                        f = creating(File.createTempFile(prefix, suffix, dir));
1366
                    } catch (IOException e) {
1367
                        throw new IOException("Failed to create a temporary directory in "+dir,e);
1368
                    }
K
kohsuke 已提交
1369

1370
                    Writer w = new FileWriter(writing(f));
S
ssogabe 已提交
1371 1372 1373 1374 1375
                    try {
                        w.write(contents);
                    } finally {
                        w.close();
                    }
K
kohsuke 已提交
1376

K
kohsuke 已提交
1377
                    return f.getAbsolutePath();
K
kohsuke 已提交
1378 1379
                }
            }));
K
kohsuke 已提交
1380
        } catch (IOException e) {
1381
            throw new IOException("Failed to create a temp file on "+remote,e);
K
kohsuke 已提交
1382
        }
K
kohsuke 已提交
1383 1384
    }

K
kohsuke 已提交
1385 1386
    /**
     * Creates a temporary directory inside the directory represented by 'this'
J
Jørgen P. Tjernø 已提交
1387 1388 1389 1390 1391 1392 1393 1394 1395
     *
     * @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 已提交
1396
     * @since 1.311
J
Jørgen P. Tjernø 已提交
1397
     * @see File#createTempFile(String, String)
K
kohsuke 已提交
1398 1399 1400
     */
    public FilePath createTempDir(final String prefix, final String suffix) throws IOException, InterruptedException {
        try {
1401
            return new FilePath(this,act(new SecureFileCallable<String>() {
1402
                private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1403 1404 1405 1406 1407 1408 1409 1410
                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) {
1411
            throw new IOException("Failed to create a temp directory on "+remote,e);
K
kohsuke 已提交
1412 1413 1414
        }
    }

K
kohsuke 已提交
1415 1416
    /**
     * Deletes this file.
1417 1418
     * @throws IOException if it exists but could not be successfully deleted
     * @return true, for a modicum of compatibility
K
kohsuke 已提交
1419
     */
K
kohsuke 已提交
1420
    public boolean delete() throws IOException, InterruptedException {
1421
        act(new SecureFileCallable<Void>() {
1422
            private static final long serialVersionUID = 1L;
1423
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1424
                Util.deleteFile(deleting(f));
1425
                return null;
K
kohsuke 已提交
1426 1427
            }
        });
1428
        return true;
K
kohsuke 已提交
1429 1430 1431 1432 1433 1434
    }

    /**
     * Checks if the file exists.
     */
    public boolean exists() throws IOException, InterruptedException {
1435
        return act(new SecureFileCallable<Boolean>() {
1436
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1437
            public Boolean invoke(File f, VirtualChannel channel) throws IOException {
1438
                return stating(f).exists();
K
kohsuke 已提交
1439 1440
            }
        });
K
kohsuke 已提交
1441 1442
    }

K
kohsuke 已提交
1443 1444 1445 1446 1447
    /**
     * 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 已提交
1448
     * @see #touch(long)
K
kohsuke 已提交
1449 1450
     */
    public long lastModified() throws IOException, InterruptedException {
1451
        return act(new SecureFileCallable<Long>() {
1452
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1453
            public Long invoke(File f, VirtualChannel channel) throws IOException {
1454
                return stating(f).lastModified();
K
kohsuke 已提交
1455 1456
            }
        });
K
kohsuke 已提交
1457 1458
    }

K
kohsuke 已提交
1459 1460 1461 1462 1463 1464
    /**
     * Creates a file (if not already exist) and sets the timestamp.
     *
     * @since 1.299
     */
    public void touch(final long timestamp) throws IOException, InterruptedException {
1465
        act(new SecureFileCallable<Void>() {
C
Christoph Kutzinski 已提交
1466
            private static final long serialVersionUID = -5094638816500738429L;
K
kohsuke 已提交
1467 1468
            public Void invoke(File f, VirtualChannel channel) throws IOException {
                if(!f.exists())
1469 1470
                    new FileOutputStream(creating(f)).close();
                if(!stating(f).setLastModified(timestamp))
K
kohsuke 已提交
1471 1472 1473 1474 1475
                    throw new IOException("Failed to set the timestamp of "+f+" to "+timestamp);
                return null;
            }
        });
    }
1476 1477
    
    private void setLastModifiedIfPossible(final long timestamp) throws IOException, InterruptedException {
1478
        String message = act(new SecureFileCallable<String>() {
1479 1480
            private static final long serialVersionUID = -828220335793641630L;
            public String invoke(File f, VirtualChannel channel) throws IOException {
1481
                if(!writing(f).setLastModified(timestamp)) {
1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497
                    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 已提交
1498

K
kohsuke 已提交
1499 1500 1501 1502
    /**
     * Checks if the file is a directory.
     */
    public boolean isDirectory() throws IOException, InterruptedException {
1503
        return act(new SecureFileCallable<Boolean>() {
1504
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1505
            public Boolean invoke(File f, VirtualChannel channel) throws IOException {
1506
                return stating(f).isDirectory();
K
kohsuke 已提交
1507 1508
            }
        });
K
kohsuke 已提交
1509
    }
K
kohsuke 已提交
1510 1511 1512
    
    /**
     * Returns the file size in bytes.
K
kohsuke 已提交
1513 1514
     *
     * @since 1.129
K
kohsuke 已提交
1515 1516
     */
    public long length() throws IOException, InterruptedException {
1517
        return act(new SecureFileCallable<Long>() {
1518
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1519
            public Long invoke(File f, VirtualChannel channel) throws IOException {
1520
                return stating(f).length();
K
kohsuke 已提交
1521 1522 1523
            }
        });
    }
K
kohsuke 已提交
1524

1525 1526
    /**
     * Returns the number of unallocated bytes in the partition of that file.
O
Oliver Gondža 已提交
1527
     * @since 1.542
1528 1529
     */
    public long getFreeDiskSpace() throws IOException, InterruptedException {
1530
        return act(new SecureFileCallable<Long>() {
1531
            private static final long serialVersionUID = 1L;
O
Oliver Gondža 已提交
1532
            @Override public Long invoke(File f, VirtualChannel channel) throws IOException {
1533 1534 1535 1536 1537 1538 1539
                return f.getFreeSpace();
            }
        });
    }

    /**
     * Returns the total number of bytes in the partition of that file.
O
Oliver Gondža 已提交
1540
     * @since 1.542
1541 1542
     */
    public long getTotalDiskSpace() throws IOException, InterruptedException {
1543
        return act(new SecureFileCallable<Long>() {
1544
            private static final long serialVersionUID = 1L;
O
Oliver Gondža 已提交
1545
            @Override public Long invoke(File f, VirtualChannel channel) throws IOException {
1546 1547 1548 1549 1550 1551 1552
                return f.getTotalSpace();
            }
        });
    }

    /**
     * Returns the number of usable bytes in the partition of that file.
O
Oliver Gondža 已提交
1553
     * @since 1.542
1554 1555
     */
    public long getUsableDiskSpace() throws IOException, InterruptedException {
1556
        return act(new SecureFileCallable<Long>() {
1557
            private static final long serialVersionUID = 1L;
O
Oliver Gondža 已提交
1558
            @Override public Long invoke(File f, VirtualChannel channel) throws IOException {
1559 1560 1561 1562 1563
                return f.getUsableSpace();
            }
        });
    }

K
kohsuke 已提交
1564 1565 1566 1567 1568
    /**
     * Sets the file permission.
     *
     * On Windows, no-op.
     *
K
kohsuke 已提交
1569 1570 1571
     * @param mask
     *      File permission mask. To simplify the permission copying,
     *      if the parameter is -1, this method becomes no-op.
1572 1573 1574 1575
     *      <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 已提交
1576
     * @since 1.303
K
kohsuke 已提交
1577
     * @see #mode()
K
kohsuke 已提交
1578 1579
     */
    public void chmod(final int mask) throws IOException, InterruptedException {
K
kohsuke 已提交
1580
        if(!isUnix() || mask==-1)   return;
1581
        act(new SecureFileCallable<Void>() {
1582
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1583
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1584
                // TODO first check for Java 7+ and use PosixFileAttributeView
1585
                _chmod(writing(f), mask);
R
rseguy 已提交
1586

K
kohsuke 已提交
1587 1588 1589 1590 1591
                return null;
            }
        });
    }

1592
    /**
1593
     * Run chmod via jnr-posix
1594 1595
     */
    private static void _chmod(File f, int mask) throws IOException {
1596 1597 1598
        // TODO WindowsPosix actually does something here (WindowsLibC._wchmod); should we let it?
        // Anyway the existing calls already skip this method if on Windows.
        if (File.pathSeparatorChar==';')  return; // noop
1599

1600
        PosixAPI.jnr().chmod(f.getAbsolutePath(),mask);
1601 1602 1603 1604
    }

    private static boolean CHMOD_WARNED = false;

K
kohsuke 已提交
1605 1606 1607 1608 1609 1610 1611 1612
    /**
     * Gets the file permission bit mask.
     *
     * @return
     *      -1 on Windows, since such a concept doesn't make sense.
     * @since 1.311
     * @see #chmod(int)
     */
1613
    public int mode() throws IOException, InterruptedException, PosixException {
K
kohsuke 已提交
1614
        if(!isUnix())   return -1;
1615
        return act(new SecureFileCallable<Integer>() {
1616
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1617
            public Integer invoke(File f, VirtualChannel channel) throws IOException {
1618
                return IOUtils.mode(stating(f));
K
kohsuke 已提交
1619 1620 1621 1622
            }
        });
    }

K
kohsuke 已提交
1623
    /**
K
kohsuke 已提交
1624 1625 1626 1627 1628 1629 1630 1631 1632
     * 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 已提交
1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648
    /**
     * 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 已提交
1649 1650
    /**
     * List up files in this directory, just like {@link File#listFiles(FileFilter)}.
K
kohsuke 已提交
1651 1652 1653 1654 1655 1656
     *
     * @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 已提交
1657
     */
K
kohsuke 已提交
1658
    public List<FilePath> list(final FileFilter filter) throws IOException, InterruptedException {
1659 1660 1661
        if (filter != null && !(filter instanceof Serializable)) {
            throw new IllegalArgumentException("Non-serializable filter of " + filter.getClass());
        }
1662
        return act(new SecureFileCallable<List<FilePath>>() {
1663
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1664
            public List<FilePath> invoke(File f, VirtualChannel channel) throws IOException {
1665
                File[] children = reading(f).listFiles(filter);
K
kohsuke 已提交
1666 1667 1668 1669 1670 1671 1672 1673
                if(children ==null)     return null;

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

                return r;
            }
1674
        }, (filter!=null?filter:this).getClass().getClassLoader());
K
kohsuke 已提交
1675 1676
    }

K
kohsuke 已提交
1677 1678 1679 1680
    /**
     * List up files in this directory that matches the given Ant-style filter.
     *
     * @param includes
K
kohsuke 已提交
1681
     *      See {@link FileSet} for the syntax. String like "foo/*.zip" or "foo/*&#42;/*.xml"
K
kohsuke 已提交
1682 1683
     * @return
     *      can be empty but always non-null.
K
kohsuke 已提交
1684 1685
     */
    public FilePath[] list(final String includes) throws IOException, InterruptedException {
B
bap2000 已提交
1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696
        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.
1697
     * @since 1.407
B
bap2000 已提交
1698 1699
     */
    public FilePath[] list(final String includes, final String excludes) throws IOException, InterruptedException {
1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714
        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 {
1715
        return act(new SecureFileCallable<FilePath[]>() {
1716
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1717
            public FilePath[] invoke(File f, VirtualChannel channel) throws IOException {
1718
                String[] files = glob(reading(f), includes, excludes, defaultExcludes);
K
kohsuke 已提交
1719 1720 1721 1722 1723 1724 1725 1726 1727 1728

                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 已提交
1729 1730
    /**
     * Runs Ant glob expansion.
K
kohsuke 已提交
1731 1732 1733
     *
     * @return
     *      A set of relative file names from the base directory.
K
kohsuke 已提交
1734
     */
1735
    private static String[] glob(File dir, String includes, String excludes, boolean defaultExcludes) throws IOException {
K
kohsuke 已提交
1736
        if(isAbsolute(includes))
W
wyukawa 已提交
1737
            throw new IOException("Expecting Ant GLOB pattern, but saw '"+includes+"'. See http://ant.apache.org/manual/Types/fileset.html for syntax");
B
bap2000 已提交
1738
        FileSet fs = Util.createFileSet(dir,includes,excludes);
1739
        fs.setDefaultexcludes(defaultExcludes);
K
kohsuke 已提交
1740 1741 1742 1743 1744
        DirectoryScanner ds = fs.getDirectoryScanner(new Project());
        String[] files = ds.getIncludedFiles();
        return files;
    }

K
kohsuke 已提交
1745 1746 1747
    /**
     * Reads this file.
     */
1748
    public InputStream read() throws IOException, InterruptedException {
K
kohsuke 已提交
1749
        if(channel==null)
1750
            return new FileInputStream(reading(new File(remote)));
K
kohsuke 已提交
1751 1752

        final Pipe p = Pipe.createRemoteToLocal();
1753
        actAsync(new SecureFileCallable<Void>() {
1754
            private static final long serialVersionUID = 1L;
1755 1756 1757 1758

            @Override
            public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
                FileInputStream fis = null;
1759
                try {
1760 1761
                    fis = new FileInputStream(reading(f));
                    Util.copyStream(fis, p.getOut());
1762 1763
                } catch (Exception x) {
                    p.error(x);
1764
                } finally {
1765 1766
                    org.apache.commons.io.IOUtils.closeQuietly(fis);
                    org.apache.commons.io.IOUtils.closeQuietly(p.getOut());
1767
                }
1768
                return null;
K
kohsuke 已提交
1769 1770 1771 1772 1773 1774
            }
        });

        return p.getIn();
    }

1775 1776
    /**
     * Reads this file from the specific offset.
J
Jesse Glick 已提交
1777
     * @since 1.586
1778
     */
1779
    public InputStream readFromOffset(final long offset) throws IOException, InterruptedException {
1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815
        if(channel ==null) {
            final RandomAccessFile raf = new RandomAccessFile(new File(remote), "r");
            try {
                raf.seek(offset);
            } catch (IOException e) {
                try {
                    raf.close();
                } catch (IOException e1) {
                    // ignore
                }
                throw e;
            }
            return new InputStream() {
                @Override
                public int read() throws IOException {
                    return raf.read();
                }

                @Override
                public void close() throws IOException {
                    raf.close();
                }

                @Override
                public int read(byte[] b, int off, int len) throws IOException {
                    return raf.read(b, off, len);
                }

                @Override
                public int read(byte[] b) throws IOException {
                    return raf.read(b);
                }
            };
        }

        final Pipe p = Pipe.createRemoteToLocal();
1816
        actAsync(new SecureFileCallable<Void>() {
1817 1818
            private static final long serialVersionUID = 1L;

1819
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1820 1821 1822
                final OutputStream out = new java.util.zip.GZIPOutputStream(p.getOut(), 8192);
                RandomAccessFile raf = null;
                try {
1823
                    raf = new RandomAccessFile(reading(f), "r");
1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846
                    raf.seek(offset);
                    byte[] buf = new byte[8192];
                    int len;
                    while ((len = raf.read(buf)) >= 0) {
                        out.write(buf, 0, len);
                    }
                    return null;
                } finally {
                    IOUtils.closeQuietly(out);
                    if (raf != null) {
                        try {
                            raf.close();
                        } catch (IOException e) {
                            // ignore
                        }
                    }
                }
            }
        });

        return new java.util.zip.GZIPInputStream(p.getIn());
    }

K
kohsuke 已提交
1847 1848 1849
    /**
     * Reads this file into a string, by using the current system encoding.
     */
1850
    public String readToString() throws IOException, InterruptedException {
K
kohsuke 已提交
1851 1852
        InputStream in = read();
        try {
1853
            return org.apache.commons.io.IOUtils.toString(in);
K
kohsuke 已提交
1854 1855 1856 1857 1858
        } finally {
            in.close();
        }
    }

K
kohsuke 已提交
1859 1860 1861
    /**
     * Writes to this file.
     * If this file already exists, it will be overwritten.
K
kohsuke 已提交
1862
     * If the directory doesn't exist, it will be created.
K
Kohsuke Kawaguchi 已提交
1863 1864 1865 1866 1867 1868 1869 1870 1871 1872
     *
     * <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 已提交
1873
     */
1874
    public OutputStream write() throws IOException, InterruptedException {
1875
        if(channel==null) {
1876
            File f = new File(remote).getAbsoluteFile();
1877 1878
            mkdirs(f.getParentFile());
            return new FileOutputStream(writing(f));
1879
        }
K
kohsuke 已提交
1880

1881
        return act(new SecureFileCallable<OutputStream>() {
1882
            private static final long serialVersionUID = 1L;
1883 1884
            public OutputStream invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
                f = f.getAbsoluteFile();
1885 1886
                mkdirs(f.getParentFile());
                FileOutputStream fos = new FileOutputStream(writing(f));
1887
                return new RemoteOutputStream(fos);
K
kohsuke 已提交
1888 1889 1890 1891
            }
        });
    }

K
kohsuke 已提交
1892 1893 1894 1895 1896 1897 1898 1899
    /**
     * 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 {
1900
        act(new SecureFileCallable<Void>() {
1901
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1902
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1903 1904
                mkdirs(f.getParentFile());
                FileOutputStream fos = new FileOutputStream(writing(f));
K
kohsuke 已提交
1905
                Writer w = encoding != null ? new OutputStreamWriter(fos, encoding) : new OutputStreamWriter(fos);
K
kohsuke 已提交
1906 1907 1908
                try {
                    w.write(content);
                } finally {
K
kohsuke 已提交
1909
                    w.close();
K
kohsuke 已提交
1910 1911 1912 1913 1914 1915
                }
                return null;
            }
        });
    }

K
kohsuke 已提交
1916 1917
    /**
     * Computes the MD5 digest of the file in hex string.
1918
     * @see Util#getDigestOf(File)
K
kohsuke 已提交
1919 1920
     */
    public String digest() throws IOException, InterruptedException {
1921
        return act(new SecureFileCallable<String>() {
1922
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1923
            public String invoke(File f, VirtualChannel channel) throws IOException {
1924
                return Util.getDigestOf(reading(f));
K
kohsuke 已提交
1925 1926 1927 1928
            }
        });
    }

1929 1930 1931 1932 1933 1934 1935 1936
    /**
     * 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");
    	}
1937
        act(new SecureFileCallable<Void>() {
1938
            private static final long serialVersionUID = 1L;
1939
            public Void invoke(File f, VirtualChannel channel) throws IOException {
1940
            	reading(f).renameTo(creating(new File(target.remote)));
1941
                return null;
K
kohsuke 已提交
1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954
            }
        });
    }

    /**
     * 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");
        }
1955
        act(new SecureFileCallable<Void>() {
1956
            private static final long serialVersionUID = 1L;
K
kohsuke 已提交
1957
            public Void invoke(File f, VirtualChannel channel) throws IOException {
K
Kohsuke Kawaguchi 已提交
1958 1959 1960 1961 1962 1963
                // JENKINS-16846: if f.getName() is the same as one of the files/directories in f,
                // then the rename op will fail
                File tmp = new File(f.getAbsolutePath()+".__rename");
                if (!f.renameTo(tmp))
                    throw new IOException("Failed to rename "+f+" to "+tmp);

K
kohsuke 已提交
1964 1965
                File t = new File(target.getRemote());
                
1966
                for(File child : reading(tmp).listFiles()) {
K
kohsuke 已提交
1967
                    File target = new File(t, child.getName());
1968
                    if(!stating(child).renameTo(creating(target)))
K
kohsuke 已提交
1969 1970
                        throw new IOException("Failed to rename "+child+" to "+target);
                }
1971
                deleting(tmp).delete();
K
kohsuke 已提交
1972
                return null;
1973 1974 1975 1976
            }
        });
    }

K
kohsuke 已提交
1977 1978 1979 1980 1981
    /**
     * Copies this file to the specified target.
     */
    public void copyTo(FilePath target) throws IOException, InterruptedException {
        try {
K
kohsuke 已提交
1982 1983 1984 1985 1986 1987 1988
            OutputStream out = target.write();
            try {
                copyTo(out);
            } finally {
                out.close();
            }
        } catch (IOException e) {
1989
            throw new IOException("Failed to copy "+this+" to "+target,e);
K
kohsuke 已提交
1990 1991 1992
        }
    }

K
kohsuke 已提交
1993
    /**
1994
     * Copies this file to the specified target, with file permissions and other meta attributes intact.
K
kohsuke 已提交
1995 1996 1997
     * @since 1.311
     */
    public void copyToWithPermission(FilePath target) throws IOException, InterruptedException {
K
kohsuke 已提交
1998
        copyTo(target);
K
kohsuke 已提交
1999 2000
        // copy file permission
        target.chmod(mode());
2001
        target.setLastModifiedIfPossible(lastModified());
K
kohsuke 已提交
2002 2003
    }

K
kohsuke 已提交
2004 2005 2006 2007 2008 2009
    /**
     * 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);

2010
        act(new SecureFileCallable<Void>() {
C
Christoph Kutzinski 已提交
2011
            private static final long serialVersionUID = 4088559042349254141L;
K
kohsuke 已提交
2012
            public Void invoke(File f, VirtualChannel channel) throws IOException {
2013 2014
                FileInputStream fis = null;
                try {
2015
                    fis = new FileInputStream(reading(f));
2016 2017 2018
                    Util.copyStream(fis,out);
                    return null;
                } finally {
2019 2020
                    org.apache.commons.io.IOUtils.closeQuietly(fis);
                    org.apache.commons.io.IOUtils.closeQuietly(out);
2021
                }
K
kohsuke 已提交
2022 2023
            }
        });
2024

K
Kohsuke Kawaguchi 已提交
2025 2026
        // make sure the writes fully got delivered to 'os' before we return.
        // this is needed because I/O operation is asynchronous
2027 2028 2029
        syncIO();
    }

K
Kohsuke Kawaguchi 已提交
2030 2031 2032 2033 2034
    /**
     * 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.
     */
2035 2036 2037
    private void syncIO() throws InterruptedException {
        try {
            if (channel!=null)
2038
                channel.syncLocalIO();
2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054
        } 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 已提交
2055 2056
    }

K
kohsuke 已提交
2057
    /**
K
kohsuke 已提交
2058 2059 2060 2061 2062
     * Remoting interface used for {@link FilePath#copyRecursiveTo(String, FilePath)}.
     *
     * TODO: this might not be the most efficient way to do the copy.
     */
    interface RemoteCopier {
K
kohsuke 已提交
2063 2064 2065 2066
        /**
         * @param fileName
         *      relative path name to the output file. Path separator must be '/'.
         */
K
kohsuke 已提交
2067 2068 2069 2070 2071
        void open(String fileName) throws IOException;
        void write(byte[] buf, int len) throws IOException;
        void close() throws IOException;
    }

K
kohsuke 已提交
2072 2073
    /**
     * Copies the contents of this directory recursively into the specified target directory.
C
Christoph Kutzinski 已提交
2074 2075 2076
     * 
     * @return
     *      the number of files copied.
K
kohsuke 已提交
2077 2078 2079 2080 2081 2082
     * @since 1.312 
     */
    public int copyRecursiveTo(FilePath target) throws IOException, InterruptedException {
        return copyRecursiveTo("**/*",target);
    }

C
Christoph Kutzinski 已提交
2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093
    /**
     * 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 已提交
2094 2095 2096 2097
    public int copyRecursiveTo(String fileMask, FilePath target) throws IOException, InterruptedException {
        return copyRecursiveTo(fileMask,null,target);
    }

K
kohsuke 已提交
2098 2099 2100
    /**
     * Copies the files that match the given file mask to the specified target node.
     *
K
kohsuke 已提交
2101 2102 2103 2104 2105
     * @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 已提交
2106 2107
     * @param excludes
     *      Files to be excluded. Can be null.
K
kohsuke 已提交
2108 2109 2110
     * @return
     *      the number of files copied.
     */
K
kohsuke 已提交
2111
    public int copyRecursiveTo(final String fileMask, final String excludes, final FilePath target) throws IOException, InterruptedException {
2112 2113 2114 2115 2116
        return copyRecursiveTo(new DirScanner.Glob(fileMask, excludes), target, fileMask);
    }

    /**
     * Copies files according to a specified scanner to a target node.
J
Jesse Glick 已提交
2117
     * @param scanner a way of enumerating some files (must be serializable for possible delivery to remote side)
2118 2119 2120
     * @param target the destination basedir
     * @param description a description of the fileset, for logging purposes
     * @return the number of files copied
K
Kohsuke Kawaguchi 已提交
2121
     * @since 1.532
2122 2123
     */
    public int copyRecursiveTo(final DirScanner scanner, final FilePath target, final String description) throws IOException, InterruptedException {
K
kohsuke 已提交
2124 2125
        if(this.channel==target.channel) {
            // local to local copy.
2126
            return act(new SecureFileCallable<Integer>() {
2127
                private static final long serialVersionUID = 1L;
K
kohsuke 已提交
2128
                public Integer invoke(File base, VirtualChannel channel) throws IOException {
2129
                    if(!base.exists())  return 0;
K
kohsuke 已提交
2130
                    assert target.channel==null;
2131 2132
                    final File dest = new File(target.remote);
                    final AtomicInteger count = new AtomicInteger();
2133 2134 2135
                    scanner.scan(base, reading(new FileVisitor() {
                        @Override
                        public void visit(File f, String relativePath) throws IOException {
2136 2137
                            if (f.isFile()) {
                                File target = new File(dest, relativePath);
2138
                                mkdirsE(target.getParentFile());
2139
                                Util.copyFile(f, writing(target));
2140
                                count.incrementAndGet();
K
kohsuke 已提交
2141
                            }
2142
                        }
2143 2144 2145

                        @Override
                        public boolean understandsSymlink() {
2146 2147
                            return true;
                        }
2148 2149 2150

                        @Override
                        public void visitSymlink(File link, String target, String relativePath) throws IOException {
2151
                            try {
2152
                                mkdirsE(new File(dest, relativePath).getParentFile());
2153
                                writing(new File(dest, target));
2154 2155
                                Util.createSymlink(dest, target, relativePath, TaskListener.NULL);
                            } catch (InterruptedException x) {
J
Jesse Glick 已提交
2156
                                throw (IOException) new IOException(x.toString()).initCause(x);
K
kohsuke 已提交
2157
                            }
2158
                            count.incrementAndGet();
K
kohsuke 已提交
2159
                        }
2160
                    }));
2161
                    return count.get();
K
kohsuke 已提交
2162 2163
                }
            });
2164 2165 2166 2167 2168
        } else
        if(this.channel==null) {
            // local -> remote copy
            final Pipe pipe = Pipe.createLocalToRemote();

2169
            Future<Void> future = target.actAsync(new SecureFileCallable<Void>() {
2170
                private static final long serialVersionUID = 1L;
2171
                public Void invoke(File f, VirtualChannel channel) throws IOException {
2172
                    try {
2173
                        readFromTar(remote + '/' + description, f,TarCompression.GZIP.extract(pipe.getIn()));
2174 2175 2176 2177
                        return null;
                    } finally {
                        pipe.getIn().close();
                    }
2178 2179
                }
            });
2180
            Future<Integer> future2 = actAsync(new SecureFileCallable<Integer>() {
2181 2182
                private static final long serialVersionUID = 1L;
                @Override public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
2183
                    return writeToTar(new File(remote), scanner, TarCompression.GZIP.compress(pipe.getOut()));
2184 2185
                }
            });
2186
            try {
2187
                // JENKINS-9540 in case the reading side failed, report that error first
2188
                future.get();
2189
                return future2.get();
2190
            } catch (ExecutionException e) {
2191
                throw new IOException(e);
2192
            }
K
kohsuke 已提交
2193
        } else {
2194 2195
            // remote -> local copy
            final Pipe pipe = Pipe.createRemoteToLocal();
K
kohsuke 已提交
2196

2197
            Future<Integer> future = actAsync(new SecureFileCallable<Integer>() {
2198
                private static final long serialVersionUID = 1L;
2199
                public Integer invoke(File f, VirtualChannel channel) throws IOException {
2200
                    try {
2201
                        return writeToTar(f, scanner, TarCompression.GZIP.compress(pipe.getOut()));
2202 2203 2204
                    } finally {
                        pipe.getOut().close();
                    }
K
kohsuke 已提交
2205 2206
                }
            });
2207
            try {
2208
                readFromTar(remote + '/' + description,new File(target.remote),TarCompression.GZIP.extract(pipe.getIn()));
2209 2210 2211 2212 2213 2214
            } 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
2215
                    throw new IOException(Functions.printThrowable(e),x);
2216
                } catch (TimeoutException _) {
2217 2218 2219 2220
                    // remote is hanging
                    throw e;
                }
            }
2221 2222 2223
            try {
                return future.get();
            } catch (ExecutionException e) {
2224
                throw new IOException(e);
2225 2226 2227 2228
            }
        }
    }

2229 2230 2231 2232 2233 2234 2235 2236

    /**
     * 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 {
2237
        return archive(ArchiverFactory.TAR, out, glob);
2238 2239 2240
    }

    public int tar(OutputStream out, FileFilter filter) throws IOException, InterruptedException {
2241
        return archive(ArchiverFactory.TAR, out, filter);
2242 2243
    }

2244 2245 2246 2247
    /**
     * 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 {
2248
        return archive(ArchiverFactory.TAR, out, scanner);
2249 2250
    }

2251 2252 2253 2254 2255 2256
    /**
     * Writes to a tar stream and stores obtained files to the base dir.
     *
     * @return
     *      number of files/directories that are written.
     */
2257
    private Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException {
2258
        Archiver tw = ArchiverFactory.TAR.create(out);
2259
        try {
2260
            scanner.scan(baseDir,reading(tw));
2261 2262 2263
        } finally {
            tw.close();
        }
2264
        return tw.countEntries();
2265 2266 2267 2268
    }

    /**
     * Reads from a tar stream and stores obtained files to the base dir.
2269
     * @since TODO supports large files > 10 GB, migration to commons-compress
2270
     */
2271
    private void readFromTar(String name, File baseDir, InputStream in) throws IOException {
2272 2273 2274
        TarArchiveInputStream t = new TarArchiveInputStream(in);
        
        // TarInputStream t = new TarInputStream(in);
2275
        try {
2276 2277
            TarArchiveEntry te;
            while ((te = t.getNextTarEntry()) != null) {
K
kohsuke 已提交
2278 2279
                File f = new File(baseDir,te.getName());
                if(te.isDirectory()) {
2280
                    mkdirs(f);
K
kohsuke 已提交
2281 2282
                } else {
                    File parent = f.getParentFile();
2283 2284
                    if (parent != null) mkdirs(parent);
                    writing(f);
K
kohsuke 已提交
2285

2286
                    if (te.isSymbolicLink()) {
K
Kohsuke Kawaguchi 已提交
2287 2288 2289
                        new FilePath(f).symlinkTo(te.getLinkName(), TaskListener.NULL);
                    } else {
                        IOUtils.copy(t,f);
2290 2291 2292 2293 2294

                        f.setLastModified(te.getModTime().getTime());
                        int mode = te.getMode()&0777;
                        if(mode!=0 && !Functions.isWindows()) // be defensive
                            _chmod(f,mode);
K
Kohsuke Kawaguchi 已提交
2295
                    }
K
kohsuke 已提交
2296 2297 2298
                }
            }
        } catch(IOException e) {
2299
            throw new IOException("Failed to extract "+name,e);
K
Kohsuke Kawaguchi 已提交
2300 2301
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // process this later
2302
            throw new IOException("Failed to extract "+name,e);
K
kohsuke 已提交
2303 2304
        } finally {
            t.close();
2305
        }
K
kohsuke 已提交
2306 2307
    }

2308 2309
    /**
     * Creates a {@link Launcher} for starting processes on the node
K
typo.  
kohsuke 已提交
2310
     * that has this file.
2311
     * @since 1.89
2312
     */
2313
    public Launcher createLauncher(TaskListener listener) throws IOException, InterruptedException {
2314 2315 2316
        if(channel==null)
            return new LocalLauncher(listener);
        else
2317 2318 2319
            return new RemoteLauncher(listener,channel,channel.call(new IsUnix()));
    }

2320
    private static final class IsUnix extends MasterToSlaveCallable<Boolean,IOException> {
2321 2322 2323 2324
        public Boolean call() throws IOException {
            return File.pathSeparatorChar==':';
        }
        private static final long serialVersionUID = 1L;
2325 2326
    }

2327
    /**
K
kohsuke 已提交
2328
     * Validates the ant file mask (like "foo/bar/*.txt, zot/*.jar")
2329 2330
     * against this directory, and try to point out the problem.
     *
K
kohsuke 已提交
2331
     * <p>
K
kohsuke 已提交
2332
     * This is useful in conjunction with {@link FormValidation}.
K
kohsuke 已提交
2333
     *
2334
     * @return
2335
     *      null if no error was found. Otherwise returns a human readable error message.
2336
     * @since 1.90
K
kohsuke 已提交
2337
     * @see #validateFileMask(FilePath, String)
2338
     * @deprecated use {@link #validateAntFileMask(String, int)} instead
2339
     */
K
kohsuke 已提交
2340
    public String validateAntFileMask(final String fileMasks) throws IOException, InterruptedException {
2341 2342 2343
        return validateAntFileMask(fileMasks, Integer.MAX_VALUE);
    }

2344 2345 2346 2347 2348 2349
    /**
     * Default bound for {@link #validateAntFileMask(String, int)}.
     * @since 1.592
     */
    public static int VALIDATE_ANT_FILE_MASK_BOUND = Integer.getInteger(FilePath.class.getName() + ".VALIDATE_ANT_FILE_MASK_BOUND", 10000);

2350 2351 2352 2353 2354 2355 2356 2357 2358
    /**
     * 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).
2359
     * @param bound a maximum number of negative operations (deliberately left vague) to perform before giving up on a precise answer; try {@link #VALIDATE_ANT_FILE_MASK_BOUND}
2360 2361 2362 2363
     * @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 {
2364
        return act(new MasterToSlaveFileCallable<String>() {
2365 2366
            private static final long serialVersionUID = 1;
            public String invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
2367 2368 2369
                if(fileMasks.startsWith("~"))
                    return Messages.FilePath_TildaDoesntWork();

K
kohsuke 已提交
2370
                StringTokenizer tokens = new StringTokenizer(fileMasks,",");
K
kohsuke 已提交
2371 2372 2373

                while(tokens.hasMoreTokens()) {
                    final String fileMask = tokens.nextToken().trim();
2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399
                    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 已提交
2400
                        }
2401
                    }
2402

2403
                    {// check the (2) above next as this is more expensive.
2404
                        // Try prepending "**/" to see if that results in a match
2405
                        FileSet fs = Util.createFileSet(reading(dir),"**/"+fileMask);
2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428
                        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 已提交
2429
                        }
2430
                    }
2431

2432 2433 2434 2435 2436 2437 2438 2439
                    {// 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 已提交
2440
                                    return Messages.FilePath_validateAntFileMask_portionMatchAndSuggest(fileMask,pattern);
2441
                                else
S
sogabe 已提交
2442
                                    return Messages.FilePath_validateAntFileMask_portionMatchButPreviousNotMatchAndSuggest(fileMask,pattern,previous);
2443 2444 2445 2446 2447
                            }

                            int idx = findSeparator(pattern);
                            if(idx<0) {// no more path component left to go back
                                if(pattern.equals(fileMask))
S
sogabe 已提交
2448
                                    return Messages.FilePath_validateAntFileMask_doesntMatchAnything(fileMask);
2449
                                else
S
sogabe 已提交
2450
                                    return Messages.FilePath_validateAntFileMask_doesntMatchAnythingAndSuggest(fileMask,pattern);
2451 2452 2453 2454 2455 2456
                            }

                            // cut off the trailing component and try again
                            previous = pattern;
                            pattern = pattern.substring(0,idx);
                        }
K
kohsuke 已提交
2457
                    }
2458
                }
K
kohsuke 已提交
2459 2460

                return null; // no error
2461
            }
2462

2463 2464 2465 2466
            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;
2467
                    long start = System.currentTimeMillis();
2468
                    @Override public synchronized boolean isCaseSensitive() {
2469
                        if (!filesIncluded.isEmpty() || !dirsIncluded.isEmpty() || ticks++ > bound || System.currentTimeMillis() - start > 5000) {
2470 2471
                            throw new Cancel();
                        }
2472 2473 2474 2475
                        filesNotIncluded.clear();
                        dirsNotIncluded.clear();
                        // notFollowedSymlinks might be large, but probably unusual
                        // scannedDirs will typically be largish, but seems to be needed
2476 2477 2478
                        return super.isCaseSensitive();
                    }
                };
2479
                ds.setBasedir(reading(dir));
2480 2481 2482 2483 2484 2485 2486 2487 2488 2489
                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);
                    }
                }
2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502
                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);
            }
2503 2504 2505
        });
    }

K
kohsuke 已提交
2506 2507 2508
    /**
     * Shortcut for {@link #validateFileMask(String)} in case the left-hand side can be null.
     */
J
Jesse Glick 已提交
2509 2510 2511
    public static FormValidation validateFileMask(@CheckForNull FilePath path, String value) throws IOException {
        if(path==null) return FormValidation.ok();
        return path.validateFileMask(value);
K
kohsuke 已提交
2512 2513 2514 2515 2516 2517 2518 2519 2520 2521
    }

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

    /**
2522
     * Checks the GLOB-style file mask. See {@link #validateAntFileMask(String)}.
2523 2524
     * Requires configure permission on ancestor AbstractProject object in request,
     * or admin permission if no such ancestor is found.
K
kohsuke 已提交
2525 2526 2527
     * @since 1.294
     */
    public FormValidation validateFileMask(String value, boolean errorIfNotExist) throws IOException {
2528
        checkPermissionForValidate();
2529

K
kohsuke 已提交
2530 2531 2532 2533 2534 2535 2536 2537
        value = fixEmpty(value);
        if(value==null)
            return FormValidation.ok();

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

2538
            String msg = validateAntFileMask(value, VALIDATE_ANT_FILE_MASK_BOUND);
K
kohsuke 已提交
2539 2540 2541
            if(errorIfNotExist)     return FormValidation.error(msg);
            else                    return FormValidation.warning(msg);
        } catch (InterruptedException e) {
2542
            return FormValidation.ok(Messages.FilePath_did_not_manage_to_validate_may_be_too_sl(value));
K
kohsuke 已提交
2543 2544 2545 2546 2547
        }
    }

    /**
     * Validates a relative file path from this {@link FilePath}.
2548 2549
     * Requires configure permission on ancestor AbstractProject object in request,
     * or admin permission if no such ancestor is found.
K
kohsuke 已提交
2550 2551 2552 2553 2554 2555 2556 2557 2558 2559
     *
     * @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 {
2560
        checkPermissionForValidate();
K
kohsuke 已提交
2561 2562 2563 2564

        value = fixEmpty(value);

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

        // a common mistake is to use wildcard
S
sogabe 已提交
2568
        if(value.contains("*")) return FormValidation.error(Messages.FilePath_validateRelativePath_wildcardNotAllowed());
K
kohsuke 已提交
2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579

        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 已提交
2580
                        return FormValidation.error(Messages.FilePath_validateRelativePath_notFile(value));
K
kohsuke 已提交
2581 2582 2583 2584
                } else {
                    if(path.isDirectory())
                        return FormValidation.ok();
                    else
S
sogabe 已提交
2585
                        return FormValidation.error(Messages.FilePath_validateRelativePath_notDirectory(value));
K
kohsuke 已提交
2586 2587 2588
                }
            }

S
sogabe 已提交
2589 2590
            String msg = expectingFile ? Messages.FilePath_validateRelativePath_noSuchFile(value) : 
                Messages.FilePath_validateRelativePath_noSuchDirectory(value);
K
kohsuke 已提交
2591 2592 2593 2594 2595 2596 2597
            if(errorIfNotExist)     return FormValidation.error(msg);
            else                    return FormValidation.warning(msg);
        } catch (InterruptedException e) {
            return FormValidation.ok();
        }
    }

2598 2599 2600
    private static void checkPermissionForValidate() {
        AccessControlled subject = Stapler.getCurrentRequest().findAncestorObject(AbstractProject.class);
        if (subject == null)
2601
            Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
2602 2603 2604 2605
        else
            subject.checkPermission(Item.CONFIGURE);
    }

K
kohsuke 已提交
2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616
    /**
     * 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);
    }

2617
    @Deprecated @Override
K
kohsuke 已提交
2618 2619
    public String toString() {
        // to make writing JSPs easily, return local
K
kohsuke 已提交
2620
        return remote;
K
kohsuke 已提交
2621 2622
    }

K
kohsuke 已提交
2623 2624
    public VirtualChannel getChannel() {
        if(channel!=null)   return channel;
K
Kohsuke Kawaguchi 已提交
2625
        else                return localChannel;
K
kohsuke 已提交
2626 2627
    }

K
kohsuke 已提交
2628 2629 2630 2631
    /**
     * Returns true if this {@link FilePath} represents a remote file. 
     */
    public boolean isRemote() {
K
typo.  
kohsuke 已提交
2632
        return channel!=null;
K
kohsuke 已提交
2633 2634
    }

K
kohsuke 已提交
2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651
    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;
2652
            this.filter = null;
K
kohsuke 已提交
2653 2654
        } else {
            this.channel = null;
2655 2656 2657 2658
            // 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.
2659
            this.filter = SoloFilePathFilter.wrap(FilePathFilter.current());
K
kohsuke 已提交
2660 2661 2662 2663
        }
    }

    private static final long serialVersionUID = 1L;
2664

2665 2666
    public static int SIDE_BUFFER_SIZE = 1024;

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

2669 2670 2671 2672 2673
    /**
     * Adapts {@link FileCallable} to {@link Callable}.
     */
    private class FileCallableWrapper<T> implements DelegatingCallable<T,IOException> {
        private final FileCallable<T> callable;
2674
        private transient ClassLoader classLoader;
2675 2676 2677

        public FileCallableWrapper(FileCallable<T> callable) {
            this.callable = callable;
2678 2679 2680 2681 2682 2683
            this.classLoader = callable.getClass().getClassLoader();
        }

        private FileCallableWrapper(FileCallable<T> callable, ClassLoader classLoader) {
            this.callable = callable;
            this.classLoader = classLoader;
2684 2685 2686
        }

        public T call() throws IOException {
2687 2688 2689 2690 2691
            try {
                return callable.invoke(new File(remote), Channel.current());
            } catch (InterruptedException e) {
                throw new TunneledInterruptedException(e);
            }
2692 2693
        }

2694 2695 2696 2697
        /**
         * Role check comes from {@link FileCallable}s.
         */
        @Override
2698 2699
        public void checkRoles(RoleChecker checker) throws SecurityException {
            callable.checkRoles(checker);
2700 2701
        }

2702
        public ClassLoader getClassLoader() {
2703
            return classLoader;
2704 2705 2706 2707
        }

        private static final long serialVersionUID = 1L;
    }
2708

2709 2710 2711
    /**
     * Used to tunnel {@link InterruptedException} over a Java signature that only allows {@link IOException}
     */
2712
    private static class TunneledInterruptedException extends IOException {
2713 2714 2715 2716 2717 2718
        private TunneledInterruptedException(InterruptedException cause) {
            super(cause);
        }
        private static final long serialVersionUID = 1L;
    }

2719 2720 2721 2722 2723
    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 已提交
2724

K
Kohsuke Kawaguchi 已提交
2725 2726 2727 2728 2729
    /**
     * 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 {
2730
        return ch.call(new MasterToSlaveCallable<FilePath,IOException>() {
K
Kohsuke Kawaguchi 已提交
2731 2732 2733 2734 2735
            public FilePath call() throws IOException {
                return new FilePath(new File(System.getProperty("user.home")));
            }
        });
    }
2736 2737 2738

    /**
     * Helper class to make it easy to send an explicit list of files using {@link FilePath} methods.
K
Kohsuke Kawaguchi 已提交
2739
     * @since 1.532
2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765
     */
    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);
            }
        }
    }

K
Kohsuke Kawaguchi 已提交
2766 2767 2768 2769 2770 2771 2772
    private static final ExecutorService threadPoolForRemoting = new ContextResettingExecutorService(
            Executors.newCachedThreadPool(
                    new ExceptionCatchingThreadFactory(
                            new NamingThreadFactory(new DaemonThreadFactory(), "FilePath.localPool"))
            ));

    public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting);
2773

2774 2775
    private @Nonnull SoloFilePathFilter filterNonNull() {
        return filter!=null ? filter : UNRESTRICTED;
2776 2777
    }

2778 2779 2780
    /**
     * Wraps {@link FileVisitor} to notify read access to {@link FilePathFilter}.
     */
2781
    private FileVisitor reading(final FileVisitor v) {
2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793
        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 已提交
2794
                filter.read(link);
2795 2796 2797 2798 2799
                v.visitSymlink(link, target, relativePath);
            }

            @Override
            public boolean understandsSymlink() {
K
Fix up.  
Kohsuke Kawaguchi 已提交
2800
                return v.understandsSymlink();
2801 2802 2803 2804 2805 2806 2807
            }
        };
    }

    /**
     * Pass through 'f' after ensuring that we can read that file.
     */
2808 2809
    private File reading(File f) {
        filterNonNull().read(f);
2810 2811 2812 2813 2814 2815
        return f;
    }

    /**
     * Pass through 'f' after ensuring that we can access the file attributes.
     */
2816 2817
    private File stating(File f) {
        filterNonNull().stat(f);
2818 2819 2820 2821 2822 2823
        return f;
    }

    /**
     * Pass through 'f' after ensuring that we can create that file/dir.
     */
2824 2825
    private File creating(File f) {
        filterNonNull().create(f);
2826 2827 2828 2829 2830 2831
        return f;
    }

    /**
     * Pass through 'f' after ensuring that we can write to that file.
     */
2832 2833
    private File writing(File f) {
        FilePathFilter filter = filterNonNull();
K
Fix up.  
Kohsuke Kawaguchi 已提交
2834 2835 2836
        if (!f.exists())
            filter.create(f);
        filter.write(f);
2837 2838 2839
        return f;
    }

2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850
    /**
     * Pass through 'f' after ensuring that we can create that symlink.
     */
    private File symlinking(File f) {
        FilePathFilter filter = filterNonNull();
        if (!f.exists())
            filter.create(f);
        filter.symlink(f);
        return f;
    }

2851
    /**
2852
     * Pass through 'f' after ensuring that we can delete that file.
2853
     */
2854 2855
    private File deleting(File f) {
        filterNonNull().delete(f);
2856 2857 2858
        return f;
    }

2859
    private boolean mkdirs(File dir) {
K
Fix up.  
Kohsuke Kawaguchi 已提交
2860 2861
        if (dir.exists())   return false;

2862
        filterNonNull().mkdirs(dir);
2863 2864
        return dir.mkdirs();
    }
2865 2866 2867 2868 2869 2870 2871 2872

    private File mkdirsE(File dir) throws IOException {
        if (dir.exists()) {
            return dir;
        }
        filterNonNull().mkdirs(dir);
        return IOUtils.mkdirs(dir);
    }
2873

2874
    private static final SoloFilePathFilter UNRESTRICTED = SoloFilePathFilter.wrap(FilePathFilter.UNRESTRICTED);
K
kohsuke 已提交
2875
}