mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-10 10:36:21 +00:00
[SECURITY-2455]
This commit is contained in:
1 parent
87a500add0
commit
63cde2daad
7 files changed
+991
-132
No files matched your search
@@ -34,7 +34,6 @@ import com.jcraft.jzlib.GZIPInputStream;
|
||||
import com.jcraft.jzlib.GZIPOutputStream;
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import edu.umd.cs.findbugs.annotations.Nullable;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import hudson.Launcher.LocalLauncher;
|
||||
import hudson.Launcher.RemoteLauncher;
|
||||
@@ -216,6 +215,11 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
*/
|
||||
private static final int MAX_REDIRECTS = 20;
|
||||
|
||||
/**
|
||||
* Escape hatch for some additional protections against sending callables intended to be locally used only
|
||||
*/
|
||||
private static /* non-final for Groovy */ boolean REJECT_LOCAL_CALLABLE_DESERIALIZATION = SystemProperties.getBoolean(FilePath.class.getName() + ".rejectLocalCallableDeserialization", true);
|
||||
|
||||
/**
|
||||
* When this {@link FilePath} represents the remote path,
|
||||
* this field is always non-null on the controller (the field represents
|
||||
@@ -239,18 +243,6 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
*/
|
||||
private /*final*/ String remote;
|
||||
|
||||
/**
|
||||
* If this {@link FilePath} is deserialized to handle file access request from a remote computer,
|
||||
* this field is set to the filter that performs access control.
|
||||
*
|
||||
* <p>
|
||||
* If null, no access control is needed.
|
||||
*
|
||||
* @see #filterNonNull()
|
||||
*/
|
||||
private transient @Nullable
|
||||
SoloFilePathFilter filter;
|
||||
|
||||
/**
|
||||
* Creates a {@link FilePath} that represents a path on the given node.
|
||||
*
|
||||
@@ -527,7 +519,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
final OutputStream out = channel != null ? new RemoteOutputStream(os) : os;
|
||||
return act(new Archive(factory, out, scanner, verificationRoot, noFollowLinks));
|
||||
}
|
||||
private class Archive extends SecureFileCallable<Integer> {
|
||||
private static class Archive extends SecureFileCallable<Integer> {
|
||||
private final ArchiverFactory factory;
|
||||
private final OutputStream out;
|
||||
private final DirScanner scanner;
|
||||
@@ -573,14 +565,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
*/
|
||||
public void unzip(final FilePath target) throws IOException, InterruptedException {
|
||||
// TODO: post release, re-unite two branches by introducing FileStreamCallable that resolves InputStream
|
||||
if (this.channel!=target.channel) {// local -> remote or remote->local
|
||||
if (channel != target.channel) {// local -> remote or remote->local
|
||||
final RemoteInputStream in = new RemoteInputStream(read(), Flag.GREEDY);
|
||||
target.act(new UnzipRemote(in));
|
||||
} else {// local -> local or remote->remote
|
||||
target.act(new UnzipLocal());
|
||||
target.act(new UnzipLocal(this));
|
||||
}
|
||||
}
|
||||
private class UnzipRemote extends SecureFileCallable<Void> {
|
||||
private static class UnzipRemote extends SecureFileCallable<Void> {
|
||||
private final RemoteInputStream in;
|
||||
UnzipRemote(RemoteInputStream in) {
|
||||
this.in = in;
|
||||
@@ -592,14 +584,30 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
}
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
private class UnzipLocal extends SecureFileCallable<Void> {
|
||||
private static class UnzipLocal extends SecureFileCallable<Void> {
|
||||
|
||||
private final FilePath filePath;
|
||||
|
||||
private UnzipLocal(FilePath filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
|
||||
assert !FilePath.this.isRemote(); // this.channel==target.channel above
|
||||
unzip(dir, reading(new File(FilePath.this.getRemote()))); // shortcut to local file
|
||||
if (this.filePath.isRemote()) {
|
||||
throw new IllegalStateException("Expected local path for file: " + filePath); // this.channel==target.channel above
|
||||
}
|
||||
unzip(dir, reading(new File(this.filePath.getRemote()))); // shortcut to local file
|
||||
return null;
|
||||
}
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected Object readResolve() {
|
||||
if (REJECT_LOCAL_CALLABLE_DESERIALIZATION) {
|
||||
throw new IllegalStateException("This callable is not intended to be sent through a channel");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -613,39 +621,52 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
* @see #untarFrom(InputStream, TarCompression)
|
||||
*/
|
||||
public void untar(final FilePath target, final TarCompression compression) throws IOException, InterruptedException {
|
||||
final FilePath source = FilePath.this;
|
||||
// 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 UntarRemote(compression, in));
|
||||
if (source.channel != target.channel) {// local -> remote or remote->local
|
||||
final RemoteInputStream in = new RemoteInputStream(source.read(), Flag.GREEDY);
|
||||
target.act(new UntarRemote(source.getName(), compression, in));
|
||||
} else {// local -> local or remote->remote
|
||||
target.act(new UntarLocal(compression));
|
||||
target.act(new UntarLocal(source, compression));
|
||||
}
|
||||
}
|
||||
private class UntarRemote extends SecureFileCallable<Void> {
|
||||
private static class UntarRemote extends SecureFileCallable<Void> {
|
||||
private final TarCompression compression;
|
||||
private final RemoteInputStream in;
|
||||
UntarRemote(TarCompression compression, RemoteInputStream in) {
|
||||
private final String name;
|
||||
UntarRemote(String name, TarCompression compression, RemoteInputStream in) {
|
||||
this.compression = compression;
|
||||
this.in = in;
|
||||
this.name = name;
|
||||
}
|
||||
@Override
|
||||
public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
|
||||
readFromTar(FilePath.this.getName(), dir, compression.extract(in));
|
||||
readFromTar(name, dir, compression.extract(in));
|
||||
return null;
|
||||
}
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
private class UntarLocal extends SecureFileCallable<Void> {
|
||||
private static class UntarLocal extends SecureFileCallable<Void> {
|
||||
private final TarCompression compression;
|
||||
UntarLocal(TarCompression compression) {
|
||||
private final FilePath filePath;
|
||||
|
||||
UntarLocal(FilePath source, TarCompression compression) {
|
||||
this.filePath = source;
|
||||
this.compression = compression;
|
||||
}
|
||||
@Override
|
||||
public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
|
||||
readFromTar(FilePath.this.getName(), dir, compression.extract(FilePath.this.read()));
|
||||
readFromTar(this.filePath.getName(), dir, compression.extract(this.filePath.read()));
|
||||
return null;
|
||||
}
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected Object readResolve() {
|
||||
if (REJECT_LOCAL_CALLABLE_DESERIALIZATION) {
|
||||
throw new IllegalStateException("This callable is not intended to be sent through a channel");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -660,7 +681,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
final InputStream in = new RemoteInputStream(_in, Flag.GREEDY);
|
||||
act(new UnzipFrom(in));
|
||||
}
|
||||
private class UnzipFrom extends SecureFileCallable<Void> {
|
||||
private static class UnzipFrom extends SecureFileCallable<Void> {
|
||||
private final InputStream in;
|
||||
UnzipFrom(InputStream in) {
|
||||
this.in = in;
|
||||
@@ -673,7 +694,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
|
||||
private void unzip(File dir, InputStream in) throws IOException {
|
||||
private static void unzip(File dir, InputStream in) throws IOException {
|
||||
File tmpFile = File.createTempFile("tmpzip", null); // uses java.io.tmpdir
|
||||
try {
|
||||
// TODO why does this not simply use ZipInputStream?
|
||||
@@ -685,7 +706,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
}
|
||||
}
|
||||
|
||||
private void unzip(File dir, File zipFile) throws IOException {
|
||||
private static void unzip(File dir, File zipFile) throws IOException {
|
||||
dir = dir.getAbsoluteFile(); // without absolutization, getParentFile below seems to fail
|
||||
ZipFile zip = new ZipFile(zipFile);
|
||||
Enumeration<ZipEntry> entries = zip.getEntries();
|
||||
@@ -734,7 +755,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public String invoke(File f, VirtualChannel channel) throws IOException {
|
||||
return f.getAbsolutePath();
|
||||
return stating(f).getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,7 +775,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
|
||||
@Override
|
||||
public Boolean invoke(File f, VirtualChannel channel) throws IOException {
|
||||
return isSymlink(f, verificationRoot, noFollowLinks);
|
||||
return isSymlink(stating(f), verificationRoot, noFollowLinks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,7 +813,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public void symlinkTo(final String target, final TaskListener listener) throws IOException, InterruptedException {
|
||||
act(new SymlinkTo(target, listener));
|
||||
}
|
||||
private class SymlinkTo extends SecureFileCallable<Void> {
|
||||
private static class SymlinkTo extends SecureFileCallable<Void> {
|
||||
private final String target;
|
||||
private final TaskListener listener;
|
||||
SymlinkTo(String target, TaskListener listener) {
|
||||
@@ -802,8 +823,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
|
||||
symlinking(f);
|
||||
Util.createSymlink(f.getParentFile(), target, f.getName(), listener);
|
||||
Util.createSymlink(symlinking(f).getParentFile(), target, f.getName(), listener);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -818,7 +838,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public String readLink() throws IOException, InterruptedException {
|
||||
return act(new ReadLink());
|
||||
}
|
||||
private class ReadLink extends SecureFileCallable<String> {
|
||||
private static class ReadLink extends SecureFileCallable<String> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
|
||||
@@ -896,7 +916,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
_in.close();
|
||||
}
|
||||
}
|
||||
private class UntarFrom extends SecureFileCallable<Void> {
|
||||
private static class UntarFrom extends SecureFileCallable<Void> {
|
||||
private final TarCompression compression;
|
||||
private final InputStream in;
|
||||
UntarFrom(TarCompression compression, InputStream in) {
|
||||
@@ -905,7 +925,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
}
|
||||
@Override
|
||||
public Void invoke(File dir, VirtualChannel channel) throws IOException {
|
||||
readFromTar("input stream",dir, compression.extract(in));
|
||||
readFromTar("input stream",dir, compression.extract(in)); // #writing etc. are called in #readFromTar
|
||||
return null;
|
||||
}
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -1157,7 +1177,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
if(channel!=null) {
|
||||
// run this on a remote system
|
||||
try {
|
||||
DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<>(callable, cl);
|
||||
DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<>(callable, cl, this);
|
||||
for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) {
|
||||
wrapper = factory.wrap(wrapper);
|
||||
}
|
||||
@@ -1233,7 +1253,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
*/
|
||||
public <T> Future<T> actAsync(final FileCallable<T> callable) throws IOException, InterruptedException {
|
||||
try {
|
||||
DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<>(callable);
|
||||
DelegatingCallable<T,IOException> wrapper = new FileCallableWrapper<>(callable, this);
|
||||
for (FileCallableWrapperFactory factory : ExtensionList.lookup(FileCallableWrapperFactory.class)) {
|
||||
wrapper = factory.wrap(wrapper);
|
||||
}
|
||||
@@ -1302,7 +1322,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public URI invoke(File f, VirtualChannel channel) {
|
||||
return f.toURI();
|
||||
return stating(f).toURI();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1340,7 +1360,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
throw new IOException("Failed to mkdirs: " + remote);
|
||||
}
|
||||
}
|
||||
private class Mkdirs extends SecureFileCallable<Boolean> {
|
||||
private static class Mkdirs extends SecureFileCallable<Boolean> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Boolean invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
|
||||
@@ -1366,7 +1386,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
/**
|
||||
* Deletes all suffixed directories that are separated by {@link WorkspaceList#COMBINATOR}, including all its contents recursively.
|
||||
*/
|
||||
private class DeleteSuffixesRecursive extends SecureFileCallable<Void> {
|
||||
private static class DeleteSuffixesRecursive extends SecureFileCallable<Void> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@@ -1398,7 +1418,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public void deleteRecursive() throws IOException, InterruptedException {
|
||||
act(new DeleteRecursive());
|
||||
}
|
||||
private class DeleteRecursive extends SecureFileCallable<Void> {
|
||||
private static class DeleteRecursive extends SecureFileCallable<Void> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1413,7 +1433,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public void deleteContents() throws IOException, InterruptedException {
|
||||
act(new DeleteContents());
|
||||
}
|
||||
private class DeleteContents extends SecureFileCallable<Void> {
|
||||
private static class DeleteContents extends SecureFileCallable<Void> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1516,7 +1536,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
throw new IOException("Failed to create a temp file on "+remote,e);
|
||||
}
|
||||
}
|
||||
private class CreateTempFile extends SecureFileCallable<String> {
|
||||
private static class CreateTempFile extends SecureFileCallable<String> {
|
||||
private final String prefix;
|
||||
private final String suffix;
|
||||
CreateTempFile(String prefix, String suffix) {
|
||||
@@ -1526,7 +1546,8 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public String invoke(File dir, VirtualChannel channel) throws IOException {
|
||||
File f = writing(File.createTempFile(prefix, suffix, dir));
|
||||
creating(new File(dir, prefix + "-security-check-dummy-" + suffix)); // use fake file to check access before creation
|
||||
File f = creating(File.createTempFile(prefix, suffix, dir));
|
||||
return f.getName();
|
||||
}
|
||||
}
|
||||
@@ -1580,7 +1601,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
throw new IOException("Failed to create a temp file on "+remote,e);
|
||||
}
|
||||
}
|
||||
private final class CreateTextTempFile extends SecureFileCallable<String> {
|
||||
private static class CreateTextTempFile extends SecureFileCallable<String> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final boolean inThisDirectory;
|
||||
private final String prefix;
|
||||
@@ -1601,6 +1622,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
|
||||
File f;
|
||||
try {
|
||||
creating(new File(dir, prefix + "-security-check-dummy-" + suffix)); // use fake file to check access before creation
|
||||
f = creating(File.createTempFile(prefix, suffix, dir));
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Failed to create a temporary directory in "+dir,e);
|
||||
@@ -1642,7 +1664,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
throw new IOException("Failed to create a temp directory on "+remote,e);
|
||||
}
|
||||
}
|
||||
private class CreateTempDir extends SecureFileCallable<String> {
|
||||
private static class CreateTempDir extends SecureFileCallable<String> {
|
||||
private final String name;
|
||||
CreateTempDir(String name) {
|
||||
this.name = name;
|
||||
@@ -1650,6 +1672,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public String invoke(File dir, VirtualChannel channel) throws IOException {
|
||||
mkdirsing(new File(dir, name + "-security-test")); // ensure access
|
||||
|
||||
Path tempPath;
|
||||
final boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
|
||||
@@ -1661,8 +1684,8 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
tempPath = Files.createTempDirectory(Util.fileToPath(dir), name);
|
||||
}
|
||||
|
||||
if (tempPath.toFile() == null) {
|
||||
throw new IOException("Failed to obtain file from path " + dir + " on " + remote);
|
||||
if (mkdirsing(tempPath.toFile()) == null) {
|
||||
throw new IOException("Failed to obtain file from path " + dir);
|
||||
}
|
||||
return tempPath.toFile().getName();
|
||||
}
|
||||
@@ -1677,7 +1700,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
act(new Delete());
|
||||
return true;
|
||||
}
|
||||
private class Delete extends SecureFileCallable<Void> {
|
||||
private static class Delete extends SecureFileCallable<Void> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1692,7 +1715,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public boolean exists() throws IOException, InterruptedException {
|
||||
return act(new Exists());
|
||||
}
|
||||
private class Exists extends SecureFileCallable<Boolean> {
|
||||
private static class Exists extends SecureFileCallable<Boolean> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Boolean invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1710,7 +1733,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public long lastModified() throws IOException, InterruptedException {
|
||||
return act(new LastModified());
|
||||
}
|
||||
private class LastModified extends SecureFileCallable<Long> {
|
||||
private static class LastModified extends SecureFileCallable<Long> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Long invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1726,7 +1749,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public void touch(final long timestamp) throws IOException, InterruptedException {
|
||||
act(new Touch(timestamp));
|
||||
}
|
||||
private class Touch extends SecureFileCallable<Void> {
|
||||
private static class Touch extends SecureFileCallable<Void> {
|
||||
private final long timestamp;
|
||||
Touch(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
@@ -1750,7 +1773,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
LOGGER.warning(message);
|
||||
}
|
||||
}
|
||||
private class SetLastModified extends SecureFileCallable<String> {
|
||||
private static class SetLastModified extends SecureFileCallable<String> {
|
||||
private final long timestamp;
|
||||
SetLastModified(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
@@ -1777,7 +1800,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public boolean isDirectory() throws IOException, InterruptedException {
|
||||
return act(new IsDirectory());
|
||||
}
|
||||
private final class IsDirectory extends SecureFileCallable<Boolean> {
|
||||
private static class IsDirectory extends SecureFileCallable<Boolean> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Boolean invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1793,7 +1816,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public long length() throws IOException, InterruptedException {
|
||||
return act(new Length());
|
||||
}
|
||||
private class Length extends SecureFileCallable<Long> {
|
||||
private static class Length extends SecureFileCallable<Long> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Long invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1808,7 +1831,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public long getFreeDiskSpace() throws IOException, InterruptedException {
|
||||
return act(new GetFreeDiskSpace());
|
||||
}
|
||||
private static class GetFreeDiskSpace extends SecureFileCallable<Long> {
|
||||
private static class GetFreeDiskSpace extends MasterToSlaveFileCallable<Long> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Long invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1823,7 +1846,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public long getTotalDiskSpace() throws IOException, InterruptedException {
|
||||
return act(new GetTotalDiskSpace());
|
||||
}
|
||||
private static class GetTotalDiskSpace extends SecureFileCallable<Long> {
|
||||
private static class GetTotalDiskSpace extends MasterToSlaveFileCallable<Long> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Long invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1838,7 +1861,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public long getUsableDiskSpace() throws IOException, InterruptedException {
|
||||
return act(new GetUsableDiskSpace());
|
||||
}
|
||||
private static class GetUsableDiskSpace extends SecureFileCallable<Long> {
|
||||
private static class GetUsableDiskSpace extends MasterToSlaveFileCallable<Long> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Long invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1870,7 +1893,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
if(!isUnix() || mask==-1) return;
|
||||
act(new Chmod(mask));
|
||||
}
|
||||
private class Chmod extends SecureFileCallable<Void> {
|
||||
private static class Chmod extends SecureFileCallable<Void> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final int mask;
|
||||
Chmod(int mask) {
|
||||
@@ -1909,7 +1932,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
if(!isUnix()) return -1;
|
||||
return act(new Mode());
|
||||
}
|
||||
private class Mode extends SecureFileCallable<Integer> {
|
||||
private static class Mode extends SecureFileCallable<Integer> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Integer invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -1979,7 +2002,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
}
|
||||
return act(new ListFilter(filter), (filter != null ? filter : this).getClass().getClassLoader());
|
||||
}
|
||||
private class ListFilter extends SecureFileCallable<List<FilePath>> {
|
||||
private static class ListFilter extends SecureFileCallable<List<FilePath>> {
|
||||
private final FileFilter filter;
|
||||
ListFilter(FileFilter filter) {
|
||||
this.filter = filter;
|
||||
@@ -2045,7 +2068,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public FilePath[] list(final String includes, final String excludes, final boolean defaultExcludes) throws IOException, InterruptedException {
|
||||
return act(new ListGlob(includes, excludes, defaultExcludes));
|
||||
}
|
||||
private class ListGlob extends SecureFileCallable<FilePath[]> {
|
||||
private static class ListGlob extends SecureFileCallable<FilePath[]> {
|
||||
private final String includes;
|
||||
private final String excludes;
|
||||
private final boolean defaultExcludes;
|
||||
@@ -2061,7 +2084,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
|
||||
FilePath[] r = new FilePath[files.length];
|
||||
for( int i=0; i<r.length; i++ )
|
||||
r[i] = new FilePath(new File(f,files[i]));
|
||||
r[i] = new FilePath(stating(new File(f,files[i])));
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -2184,7 +2207,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
return false;
|
||||
}
|
||||
|
||||
private class Read extends SecureFileCallable<Void> {
|
||||
private static class Read extends SecureFileCallable<Void> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final Pipe p;
|
||||
private String verificationRoot;
|
||||
@@ -2251,7 +2274,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
return new java.util.zip.GZIPInputStream(p.getIn());
|
||||
}
|
||||
|
||||
private class OffsetPipeSecureFileCallable extends SecureFileCallable<Void> {
|
||||
private static class OffsetPipeSecureFileCallable extends SecureFileCallable<Void> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Pipe p;
|
||||
@@ -2284,7 +2307,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public String readToString() throws IOException, InterruptedException {
|
||||
return act(new ReadToString());
|
||||
}
|
||||
private final class ReadToString extends SecureFileCallable<String> {
|
||||
private static class ReadToString extends SecureFileCallable<String> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public String invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
|
||||
@@ -2308,12 +2331,12 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
if(channel==null) {
|
||||
File f = new File(remote).getAbsoluteFile();
|
||||
mkdirs(f.getParentFile());
|
||||
return Files.newOutputStream(fileToPath(writing(f)));
|
||||
return Files.newOutputStream(fileToPath(writing(f))); // TODO #writing seems unnecessary on a local file
|
||||
}
|
||||
|
||||
return act(new WritePipe());
|
||||
}
|
||||
private class WritePipe extends SecureFileCallable<OutputStream> {
|
||||
private static class WritePipe extends SecureFileCallable<OutputStream> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public OutputStream invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
|
||||
@@ -2333,7 +2356,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public void write(final String content, final String encoding) throws IOException, InterruptedException {
|
||||
act(new Write(encoding, content));
|
||||
}
|
||||
private class Write extends SecureFileCallable<Void> {
|
||||
private static class Write extends SecureFileCallable<Void> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String encoding;
|
||||
private final String content;
|
||||
@@ -2359,7 +2382,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
public String digest() throws IOException, InterruptedException {
|
||||
return act(new Digest());
|
||||
}
|
||||
private class Digest extends SecureFileCallable<String> {
|
||||
private static class Digest extends SecureFileCallable<String> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public String invoke(File f, VirtualChannel channel) throws IOException {
|
||||
@@ -2377,7 +2400,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
}
|
||||
act(new RenameTo(target));
|
||||
}
|
||||
private class RenameTo extends SecureFileCallable<Void> {
|
||||
private static class RenameTo extends SecureFileCallable<Void> {
|
||||
private final FilePath target;
|
||||
RenameTo(FilePath target) {
|
||||
this.target = target;
|
||||
@@ -2385,7 +2408,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
||||
Files.move(fileToPath(reading(f)), fileToPath(creating(new File(target.remote))), LinkOption.NOFOLLOW_LINKS);
|
||||
Files.move(fileToPath(deleting(reading(f))), fileToPath(writing(creating(new File(target.remote)))), LinkOption.NOFOLLOW_LINKS);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2401,7 +2424,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
}
|
||||
act(new MoveAllChildrenTo(target));
|
||||
}
|
||||
private class MoveAllChildrenTo extends SecureFileCallable<Void> {
|
||||
private static class MoveAllChildrenTo extends SecureFileCallable<Void> {
|
||||
private final FilePath target;
|
||||
MoveAllChildrenTo(FilePath target) {
|
||||
this.target = target;
|
||||
@@ -2412,14 +2435,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
// 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))
|
||||
if (!deleting(f).renameTo(creating(tmp)))
|
||||
throw new IOException("Failed to rename "+f+" to "+tmp);
|
||||
|
||||
File t = new File(target.getRemote());
|
||||
|
||||
for(File child : reading(tmp).listFiles()) {
|
||||
File target = new File(t, child.getName());
|
||||
if(!stating(child).renameTo(creating(target)))
|
||||
if(!deleting(reading(child)).renameTo(writing(creating(target))))
|
||||
throw new IOException("Failed to rename "+child+" to "+target);
|
||||
}
|
||||
deleting(tmp).delete();
|
||||
@@ -2456,7 +2479,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
target.chmod(mode());
|
||||
target.setLastModifiedIfPossible(lastModified());
|
||||
}
|
||||
private class CopyToWithPermission extends SecureFileCallable<Void> {
|
||||
private static class CopyToWithPermission extends SecureFileCallable<Void> {
|
||||
private final FilePath target;
|
||||
CopyToWithPermission(FilePath target) {
|
||||
this.target = target;
|
||||
@@ -2484,7 +2507,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
// this is needed because I/O operation is asynchronous
|
||||
syncIO();
|
||||
}
|
||||
private class CopyTo extends SecureFileCallable<Void> {
|
||||
private static class CopyTo extends SecureFileCallable<Void> {
|
||||
private static final long serialVersionUID = 4088559042349254141L;
|
||||
private final OutputStream out;
|
||||
CopyTo(OutputStream out) {
|
||||
@@ -2616,7 +2639,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
// local -> remote copy
|
||||
final Pipe pipe = Pipe.createLocalToRemote();
|
||||
|
||||
Future<Void> future = target.actAsync(new ReadToTar(pipe, description, compression));
|
||||
Future<Void> future = target.actAsync(new ReadFromTar(target, pipe, description, compression));
|
||||
Future<Integer> future2 = actAsync(new WriteToTar(scanner, pipe, compression));
|
||||
try {
|
||||
// JENKINS-9540 in case the reading side failed, report that error first
|
||||
@@ -2662,7 +2685,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
;
|
||||
}
|
||||
|
||||
private class CopyRecursiveLocal extends SecureFileCallable<Integer> {
|
||||
private static class CopyRecursiveLocal extends SecureFileCallable<Integer> {
|
||||
private final FilePath target;
|
||||
private final DirScanner scanner;
|
||||
CopyRecursiveLocal(FilePath target, DirScanner scanner) {
|
||||
@@ -2675,7 +2698,9 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
if (!base.exists()) {
|
||||
return 0;
|
||||
}
|
||||
assert target.channel == null;
|
||||
if (target.channel != null) {
|
||||
throw new IllegalStateException("Expected null channel for " + target);
|
||||
}
|
||||
final File dest = new File(target.remote);
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
scanner.scan(base, reading(new FileVisitor() {
|
||||
@@ -2729,12 +2754,14 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
return count.get();
|
||||
}
|
||||
}
|
||||
private class ReadToTar extends SecureFileCallable<Void> {
|
||||
private static class ReadFromTar extends SecureFileCallable<Void> {
|
||||
private final Pipe pipe;
|
||||
private final String description;
|
||||
private final TarCompression compression;
|
||||
private final FilePath target;
|
||||
|
||||
ReadToTar(Pipe pipe, String description, @NonNull TarCompression compression) {
|
||||
ReadFromTar(FilePath target, Pipe pipe, String description, @NonNull TarCompression compression) {
|
||||
this.target = target;
|
||||
this.pipe = pipe;
|
||||
this.description = description;
|
||||
this.compression = compression;
|
||||
@@ -2743,12 +2770,12 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
@Override
|
||||
public Void invoke(File f, VirtualChannel channel) throws IOException {
|
||||
try (InputStream in = pipe.getIn()) {
|
||||
readFromTar(remote + '/' + description, f, compression.extract(in));
|
||||
readFromTar(target.remote + '/' + description, f, compression.extract(in));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
private class WriteToTar extends SecureFileCallable<Integer> {
|
||||
private static class WriteToTar extends SecureFileCallable<Integer> {
|
||||
private final DirScanner scanner;
|
||||
private final Pipe pipe;
|
||||
private final TarCompression compression;
|
||||
@@ -2760,10 +2787,10 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Override
|
||||
public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
|
||||
return writeToTar(new File(remote), scanner, compression.compress(pipe.getOut()));
|
||||
return writeToTar(reading(f), scanner, compression.compress(pipe.getOut()));
|
||||
}
|
||||
}
|
||||
private class CopyRecursiveRemoteToLocal extends SecureFileCallable<Integer> {
|
||||
private static class CopyRecursiveRemoteToLocal extends SecureFileCallable<Integer> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final Pipe pipe;
|
||||
private final DirScanner scanner;
|
||||
@@ -2776,7 +2803,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
@Override
|
||||
public Integer invoke(File f, VirtualChannel channel) throws IOException {
|
||||
try (OutputStream out = pipe.getOut()) {
|
||||
return writeToTar(f, scanner, compression.compress(out));
|
||||
return writeToTar(reading(f), scanner, compression.compress(out));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2808,7 +2835,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
* @return
|
||||
* number of files/directories that are written.
|
||||
*/
|
||||
private Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException {
|
||||
private static Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException {
|
||||
Archiver tw = ArchiverFactory.TAR.create(out);
|
||||
try {
|
||||
scanner.scan(baseDir,reading(tw));
|
||||
@@ -2822,14 +2849,15 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
* Reads from a tar stream and stores obtained files to the base dir.
|
||||
* Supports large files > 10 GB since 1.627 when this was migrated to use commons-compress.
|
||||
*/
|
||||
private void readFromTar(String name, File baseDir, InputStream in) throws IOException {
|
||||
private static void readFromTar(String name, File baseDir, InputStream in) throws IOException {
|
||||
baseDir = baseDir.getCanonicalFile();
|
||||
|
||||
// TarInputStream t = new TarInputStream(in);
|
||||
try (TarArchiveInputStream t = new TarArchiveInputStream(in)) {
|
||||
TarArchiveEntry te;
|
||||
while ((te = t.getNextTarEntry()) != null) {
|
||||
File f = new File(baseDir, te.getName());
|
||||
if (!f.toPath().normalize().startsWith(baseDir.toPath())) {
|
||||
File f = new File(baseDir, te.getName()).getCanonicalFile();
|
||||
if (!f.toPath().startsWith(baseDir.toPath())) {
|
||||
throw new IOException(
|
||||
"Tar " + name + " contains illegal file name that breaks out of the target directory: " + te.getName());
|
||||
}
|
||||
@@ -2838,12 +2866,11 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
} else {
|
||||
File parent = f.getParentFile();
|
||||
if (parent != null) mkdirs(parent);
|
||||
writing(f);
|
||||
|
||||
if (te.isSymbolicLink()) {
|
||||
new FilePath(f).symlinkTo(te.getLinkName(), TaskListener.NULL);
|
||||
new FilePath(symlinking(f)).symlinkTo(te.getLinkName(), TaskListener.NULL);
|
||||
} else {
|
||||
IOUtils.copy(t, f);
|
||||
IOUtils.copy(t, writing(f));
|
||||
|
||||
f.setLastModified(te.getModTime().getTime());
|
||||
int mode = te.getMode() & 0777;
|
||||
@@ -3280,14 +3307,12 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
ois.defaultReadObject();
|
||||
if(ois.readBoolean()) {
|
||||
this.channel = channel;
|
||||
this.filter = null;
|
||||
} else {
|
||||
this.channel = null;
|
||||
// If the remote channel wants us to create a FilePath that points to a local file,
|
||||
// we need to make sure the access control takes place.
|
||||
// This covers the immediate case of FileCallables taking FilePath into reference closure implicitly,
|
||||
// but it also covers more general case of FilePath sent as a return value or argument.
|
||||
this.filter = SoloFilePathFilter.wrap(FilePathFilter.current());
|
||||
// Any FileCallables acting on a deserialized FilePath need to ensure they're subjecting it to
|
||||
// access control checks like #reading(File) etc.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3301,24 +3326,27 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
/**
|
||||
* Adapts {@link FileCallable} to {@link Callable}.
|
||||
*/
|
||||
private class FileCallableWrapper<T> implements DelegatingCallable<T,IOException> {
|
||||
private static class FileCallableWrapper<T> implements DelegatingCallable<T,IOException> {
|
||||
private final FileCallable<T> callable;
|
||||
private transient ClassLoader classLoader;
|
||||
private final FilePath filePath;
|
||||
|
||||
FileCallableWrapper(FileCallable<T> callable) {
|
||||
FileCallableWrapper(FileCallable<T> callable, FilePath filePath) {
|
||||
this.callable = callable;
|
||||
this.classLoader = callable.getClass().getClassLoader();
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
private FileCallableWrapper(FileCallable<T> callable, ClassLoader classLoader) {
|
||||
private FileCallableWrapper(FileCallable<T> callable, ClassLoader classLoader, FilePath filePath) {
|
||||
this.callable = callable;
|
||||
this.classLoader = classLoader;
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T call() throws IOException {
|
||||
try {
|
||||
return callable.invoke(new File(remote), Channel.current());
|
||||
return callable.invoke(new File(filePath.remote), filePath.channel);
|
||||
} catch (InterruptedException e) {
|
||||
throw new TunneledInterruptedException(e);
|
||||
}
|
||||
@@ -3413,15 +3441,16 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
@NonNull
|
||||
public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting);
|
||||
|
||||
private @NonNull SoloFilePathFilter filterNonNull() {
|
||||
return filter!=null ? filter : UNRESTRICTED;
|
||||
private static @NonNull SoloFilePathFilter filterNonNull() {
|
||||
final SoloFilePathFilter filter = SoloFilePathFilter.wrap(FilePathFilter.current());
|
||||
return filter != null ? filter : UNRESTRICTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps {@link FileVisitor} to notify read access to {@link FilePathFilter}.
|
||||
*/
|
||||
private FileVisitor reading(final FileVisitor v) {
|
||||
final FilePathFilter filter = FilePathFilter.current();
|
||||
private static FileVisitor reading(final FileVisitor v) {
|
||||
final FilePathFilter filter = SoloFilePathFilter.wrap(FilePathFilter.current());
|
||||
if (filter==null) return v;
|
||||
|
||||
return new FileVisitor() {
|
||||
@@ -3470,7 +3499,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
/**
|
||||
* Pass through 'f' after ensuring that we can read that file.
|
||||
*/
|
||||
private File reading(File f) {
|
||||
private static File reading(File f) {
|
||||
filterNonNull().read(f);
|
||||
return f;
|
||||
}
|
||||
@@ -3478,7 +3507,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
/**
|
||||
* Pass through 'f' after ensuring that we can access the file attributes.
|
||||
*/
|
||||
private File stating(File f) {
|
||||
private static File stating(File f) {
|
||||
filterNonNull().stat(f);
|
||||
return f;
|
||||
}
|
||||
@@ -3486,7 +3515,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
/**
|
||||
* Pass through 'f' after ensuring that we can create that file/dir.
|
||||
*/
|
||||
private File creating(File f) {
|
||||
private static File creating(File f) {
|
||||
filterNonNull().create(f);
|
||||
return f;
|
||||
}
|
||||
@@ -3494,7 +3523,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
/**
|
||||
* Pass through 'f' after ensuring that we can write to that file.
|
||||
*/
|
||||
private File writing(File f) {
|
||||
private static File writing(File f) {
|
||||
FilePathFilter filter = filterNonNull();
|
||||
if (!f.exists())
|
||||
filter.create(f);
|
||||
@@ -3505,7 +3534,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
/**
|
||||
* Pass through 'f' after ensuring that we can create that symlink.
|
||||
*/
|
||||
private File symlinking(File f) {
|
||||
private static File symlinking(File f) {
|
||||
FilePathFilter filter = filterNonNull();
|
||||
if (!f.exists())
|
||||
filter.create(f);
|
||||
@@ -3516,24 +3545,40 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
/**
|
||||
* Pass through 'f' after ensuring that we can delete that file.
|
||||
*/
|
||||
private File deleting(File f) {
|
||||
private static File deleting(File f) {
|
||||
filterNonNull().delete(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
private boolean mkdirs(File dir) throws IOException {
|
||||
/**
|
||||
* Pass through 'f' after ensuring that we can mkdirs that directory.
|
||||
*/
|
||||
private static File mkdirsing(File f) {
|
||||
filterNonNull().mkdirs(f);
|
||||
return f;
|
||||
}
|
||||
|
||||
private static boolean mkdirs(File dir) throws IOException {
|
||||
if (dir.exists()) return false;
|
||||
|
||||
filterNonNull().mkdirs(dir);
|
||||
File reference = dir;
|
||||
while (reference != null && !reference.exists()) {
|
||||
filterNonNull().mkdirs(reference);
|
||||
reference = reference.getParentFile();
|
||||
}
|
||||
Files.createDirectories(fileToPath(dir));
|
||||
return true;
|
||||
}
|
||||
|
||||
private File mkdirsE(File dir) throws IOException {
|
||||
private static File mkdirsE(File dir) throws IOException {
|
||||
if (dir.exists()) {
|
||||
return dir;
|
||||
}
|
||||
filterNonNull().mkdirs(dir);
|
||||
File reference = dir;
|
||||
while (reference != null && !reference.exists()) {
|
||||
filterNonNull().mkdirs(reference);
|
||||
reference = reference.getParentFile();
|
||||
}
|
||||
return IOUtils.mkdirs(dir);
|
||||
}
|
||||
|
||||
@@ -3561,7 +3606,7 @@ public final class FilePath implements SerializableOnlyOverRemoting {
|
||||
throw new IllegalArgumentException("Only a relative path is supported, the given path is absolute: " + potentialChildRelativePath);
|
||||
}
|
||||
|
||||
Path parentAbsolutePath = Util.fileToPath(parentFile.getAbsoluteFile());
|
||||
Path parentAbsolutePath = Util.fileToPath(stating(parentFile).getAbsoluteFile());
|
||||
Path parentRealPath;
|
||||
try {
|
||||
if (Functions.isWindows()) {
|
||||
|
||||
Reference in new issue
Block a user